510 Commits

Author SHA1 Message Date
Renovate Bot
81f1da9aa7 fix(deps): update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.20.0 2025-01-02 20:03:05 +00:00
d9b07c3e1b API: oops wrong Sentry depend
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m31s
2024-04-27 03:21:22 -04:00
80df977047 Merge remote-tracking branch 'origin/master'
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 31s
2024-04-27 03:19:50 -04:00
526d68763f API: Capture unhandled exceptions with Sentry 2024-04-27 03:19:43 -04:00
72a2a4bb3a Merge pull request 'fix(deps): update dependency @sentry/nextjs to v7.112.2' (#25) from renovate/sentry-javascript-monorepo into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m27s
Reviewed-on: #25
2024-04-27 00:16:38 -07:00
855b040e8f API: Add Sentry
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m48s
2024-04-27 03:08:22 -04:00
Renovate Bot
31f5ca464d fix(deps): update dependency @sentry/nextjs to v7.112.2 2024-04-27 07:04:13 +00:00
e0c92ca95a Add Sentry (:
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m18s
2024-04-27 02:54:59 -04:00
57dc61f745 Frontend: Make the Mojang status page more responsive
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m13s
2024-04-26 22:50:55 -04:00
b06cbdddda Frontend: Fix footer links not working 2024-04-26 22:44:58 -04:00
5292da63b5 Frontend: Make the server search result more responsive
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m23s
2024-04-26 21:15:13 -04:00
39407ac8b0 Frontend: Show the country of a server in the result 2024-04-26 21:12:12 -04:00
2e319488a9 Frontend: Make the Mojang status page more responsive 2024-04-26 20:58:31 -04:00
490b04ba61 Frontend: Make the navbar more responsive 2024-04-26 20:55:49 -04:00
531c18626a Frontend: Improve mobile support 2024-04-26 20:53:25 -04:00
1570ade4c0 Fix rel including undefined 2024-04-26 20:12:37 -04:00
4675708a8b oops, fix new tab prop not working properly 2024-04-26 20:03:54 -04:00
295081431b Remove weird white spots in the dark wool transition 2024-04-26 20:02:57 -04:00
01743d2499 Custom link component & license header 2024-04-26 19:56:07 -04:00
7e7ac42b8e Remove .fleet, oops
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 32s
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 56s
2024-04-26 19:30:04 -04:00
08acdaef03 Java SDK: Add DNS records to MC servers
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 29s
2024-04-26 17:22:20 -04:00
ad215b7b72 Update .gitignore files
All checks were successful
Publish JS SDK / docker (push) Successful in 20s
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m23s
2024-04-26 17:01:38 -04:00
9d594a8c37 JS SDK: Add Geo and query data to servers
Some checks failed
Publish JS SDK / docker (push) Failing after 18s
2024-04-26 17:00:40 -04:00
856e445182 Bump JS SDK ver 2024-04-26 17:00:22 -04:00
ae94af5980 Import cleanup
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m42s
2024-04-26 16:41:54 -04:00
00a9dc0ce8 Prevent logins 2024-04-26 15:13:09 -04:00
fce1fa24b1 Update README.md 2024-04-26 15:08:41 -04:00
a2615ac96a Fix error and update to Java 17 2024-04-26 15:08:23 -04:00
1962110dbd Spigot -> Velocity 2024-04-26 14:45:52 -04:00
758a3bf0aa Merge pull request 'fix(deps): update dependency remote-mdx to ^0.0.7' (#22) from renovate/remote-mdx-0.x into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m36s
Reviewed-on: #22
2024-04-26 11:03:56 -07:00
7abd00a692 Merge pull request 'chore(deps): update dependency org.apache.maven.plugins:maven-assembly-plugin to v3.7.1' (#24) from renovate/org.apache.maven.plugins-maven-assembly-plugin-3.x into master
Reviewed-on: #24
2024-04-26 11:03:50 -07:00
e4c92cc0e0 Delete .gitea/workflows/deploy-demo-plugin.yml 2024-04-26 11:03:11 -07:00
Renovate Bot
f9614bacf7 chore(deps): update dependency org.apache.maven.plugins:maven-assembly-plugin to v3.7.1 2024-04-26 18:02:52 +00:00
Renovate Bot
debc8a43bf fix(deps): update dependency remote-mdx to ^0.0.7 2024-04-26 18:02:49 +00:00
6d3418e16c Update .gitea/workflows/deploy-demo-plugin.yml
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 33s
2024-04-26 11:00:21 -07:00
bb9a3044a7 Update .gitea/workflows/deploy-demo-plugin.yml
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 21s
2024-04-26 10:58:37 -07:00
d7076c6fa6 Update .gitea/workflows/deploy-demo-plugin.yml
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 22s
2024-04-26 10:57:48 -07:00
f79d42a45e Build demo plugin jar to /jars
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 35s
2024-04-26 13:55:59 -04:00
58045b19e1 Update .gitea/workflows/deploy-demo-plugin.yml
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 32s
2024-04-26 10:40:58 -07:00
0028f27c20 Update .gitea/workflows/deploy-demo-plugin.yml
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 33s
2024-04-26 10:39:50 -07:00
83aee00a72 Merge pull request 'chore(deps): update rexlmanu/pterodactyl-upload-action action to v2.4' (#21) from renovate/rexlmanu-pterodactyl-upload-action-2.x into master
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 1m36s
Reviewed-on: #21
2024-04-26 10:36:01 -07:00
fed80a0aa7 Merge pull request 'fix(deps): update dependency lucide-react to ^0.376.0' (#23) from renovate/lucide-react-0.x into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 3m30s
Reviewed-on: #23
2024-04-26 10:35:36 -07:00
Renovate Bot
f70503f80b fix(deps): update dependency lucide-react to ^0.376.0 2024-04-26 17:03:24 +00:00
Renovate Bot
a615239386 chore(deps): update rexlmanu/pterodactyl-upload-action action to v2.4 2024-04-25 22:07:36 +00:00
0cf97926ea Update demo plugin workflow
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 39s
2024-04-25 17:44:36 -04:00
acb91cc829 Update demo plugin workflow
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 39s
2024-04-25 17:42:53 -04:00
790beafce4 Update demo plugin workflow
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 35s
2024-04-25 17:21:29 -04:00
211bfd0d2b Update demo plugin workflow
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 37s
2024-04-25 17:17:25 -04:00
bb416a8734 Update demo plugin workflow
Some checks failed
Deploy Demo Plugin / docker (17, 3.8.5) (push) Failing after 6s
2024-04-25 17:16:10 -04:00
2be04ec1d6 Try absolute dir for the remote
Some checks failed
Deploy Demo Plugin / docker (17, 3.8.5) (push) Failing after 39s
2024-04-25 17:07:18 -04:00
bbeeed8f84 Merge
Some checks failed
Deploy Demo Plugin / docker (17, 3.8.5) (push) Failing after 58s
2024-04-25 17:04:53 -04:00
4e2e0f328c Update demo plugin workflow 2024-04-25 17:03:40 -04:00
16b295a784 Delete .gitea/workflows/deploy-demo-plugin.yml 2024-04-24 17:46:43 -07:00
dbd6e825f1 Update .gitea/workflows/deploy-demo-plugin.yml
Some checks failed
Deploy Demo Plugin / docker (17, 3.8.5) (push) Failing after 1m1s
2024-04-24 17:25:00 -07:00
a43c40d88f Update .gitea/workflows/deploy-demo-plugin.yml
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 1m19s
2024-04-24 17:22:46 -07:00
2783396446 Update .gitea/workflows/deploy-demo-plugin.yml
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 55s
2024-04-24 17:12:08 -07:00
494266d2ea Update .gitea/workflows/deploy-demo-plugin.yml
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 1m19s
2024-04-24 17:03:52 -07:00
4069932f0d Push to FTP for demo plugin
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 58s
2024-04-24 19:43:08 -04:00
1a07fbedb6 Build project without version 2024-04-24 19:40:17 -04:00
0655c0b651 Update MOTD msgs 2024-04-24 19:39:18 -04:00
2494d9ab45 Add demo plugin publish workflow
All checks were successful
Deploy Demo Plugin / docker (17, 3.8.5) (push) Successful in 38s
2024-04-24 19:37:19 -04:00
8b332ab400 Make Maven shut up
Some checks failed
Deploy Discord Bot / docker (17, 3.8.5) (push) Failing after 59s
2024-04-24 19:22:00 -04:00
477ac04d91 Fix missing main class in build jar
Some checks failed
Deploy Discord Bot / docker (17, 3.8.5) (push) Failing after 1m3s
2024-04-24 19:21:17 -04:00
104dc11e35 err logging
Some checks failed
Deploy Discord Bot / docker (17, 3.8.5) (push) Failing after 55s
2024-04-24 19:16:04 -04:00
4fbc977034 Fix output jar name
Some checks failed
Deploy Discord Bot / docker (17, 3.8.5) (push) Failing after 1m4s
2024-04-24 19:12:47 -04:00
409b273edc Don't use this
Some checks failed
Deploy Discord Bot / docker (17, 3.8.5) (push) Failing after 1m4s
2024-04-24 19:10:50 -04:00
f943ca54fb Add deploy bot workflow
Some checks failed
Deploy Discord Bot / docker (17, 3.8.5) (push) Failing after 38s
2024-04-24 19:05:59 -04:00
a683b7225e Add Dockerfile 2024-04-24 19:04:53 -04:00
6c8d486052 Add the server lookup command 2024-04-24 19:01:07 -04:00
1474598a1a Update player embed 2024-04-24 18:09:33 -04:00
0ba927b7d1 Discord player lookups 2024-04-24 18:01:08 -04:00
e6a12c3675 Discord bot project
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 34s
2024-04-24 16:05:39 -04:00
f9bd49c4bb Fallback to null for addr not found in MaxMind
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m43s
2024-04-24 15:42:44 -04:00
43d1ea9ddd Fix server Geo data not being fetched for numeric ips 2024-04-24 15:42:28 -04:00
77e5daa375 Add #getMojangStatus to the Java SDK
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 33s
2024-04-24 15:19:52 -04:00
f08ff7602f Merge pull request 'chore(deps): update nextjs monorepo to v14.2.3' (#20) from renovate/nextjs-monorepo into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m25s
Reviewed-on: #20
2024-04-24 11:48:14 -07:00
d929d948ab Add server responses
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 30s
2024-04-24 14:47:47 -04:00
10669f1b4a Add Minecraft server lookups to the Java SDK 2024-04-24 14:29:37 -04:00
Renovate Bot
ffa65b8ee4 chore(deps): update nextjs monorepo to v14.2.3 2024-04-24 18:03:09 +00:00
cf5ee56b64 Complete the player response 2024-04-24 13:56:32 -04:00
6b38e9f301 Merge pull request 'fix(deps): update dependency lucide-react to ^0.373.0' (#19) from renovate/lucide-react-0.x into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m33s
Reviewed-on: #19
2024-04-24 10:31:35 -07:00
cd7c178250 Merge pull request 'chore(deps): update dependency org.apache.maven.plugins:maven-shade-plugin to v3.5.3' (#18) from renovate/org.apache.maven.plugins-maven-shade-plugin-3.x into master
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 45s
Reviewed-on: #18
2024-04-24 10:31:29 -07:00
Renovate Bot
72bf0cdcd0 fix(deps): update dependency lucide-react to ^0.373.0 2024-04-24 09:02:51 +00:00
Renovate Bot
fecd1bfade chore(deps): update dependency org.apache.maven.plugins:maven-shade-plugin to v3.5.3 2024-04-23 20:02:45 +00:00
d35443db33 Update demo server version name 2024-04-23 11:56:55 -04:00
40526cd0f8 Merge pull request 'fix(deps): update dependency com.comphenix.protocol:protocollib to v5' (#17) from renovate/com.comphenix.protocol-protocollib-5.x into master
Reviewed-on: #17
2024-04-23 08:40:14 -07:00
Renovate Bot
2f94355398 fix(deps): update dependency com.comphenix.protocol:protocollib to v5 2024-04-23 08:02:28 +00:00
51dcc75a86 Update .gitea/workflows/publish-java-sdk.yml
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 25s
2024-04-23 00:57:43 -07:00
d9c09a1aae Javadoc stylesheet
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 24s
2024-04-23 03:56:38 -04:00
0a530413c4 Update pom.xml
All checks were successful
Publish Java SDK / docker (17, 3.8.5) (push) Successful in 32s
2024-04-23 03:55:56 -04:00
9dc5ad029b Merge remote-tracking branch 'origin/master'
Some checks failed
Publish Java SDK / docker (17, 3.8.5) (push) Failing after 46s
2024-04-23 03:53:39 -04:00
ab7f6162fb Update README.md 2024-04-23 00:53:36 -07:00
743f789a7f Merge remote-tracking branch 'origin/master' 2024-04-23 03:43:41 -04:00
7b1745822d Add Java SDK workflow 2024-04-23 03:43:20 -04:00
e21db5c876 Merge pull request 'fix(deps): update dependency remote-mdx to ^0.0.5' (#15) from renovate/remote-mdx-0.x into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m10s
Reviewed-on: #15
2024-04-23 00:31:32 -07:00
8e43044877 Java SDK base 2024-04-23 03:29:09 -04:00
Renovate Bot
a4fad0af0a fix(deps): update dependency remote-mdx to ^0.0.5 2024-04-23 07:04:04 +00:00
88705f363b Fix MaxMind NPE
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 26s
2024-04-23 01:36:33 -04:00
db88533eed me.braydon -> cc.restfulmc 2024-04-23 01:34:43 -04:00
68c932c39b Move tests
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Has been cancelled
2024-04-23 01:33:10 -04:00
0f9bea22f5 me.braydon -> cc.restfulmc
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Has been cancelled
2024-04-23 01:32:42 -04:00
10ab624d63 Add docker-compose.yml 2024-04-22 22:26:55 -07:00
53f66cd7e4 Merge pull request 'chore(deps): update dependency org.apache.maven.plugins:maven-shade-plugin to v3.5.2' (#13) from renovate/org.apache.maven.plugins-maven-shade-plugin-3.x into master
Reviewed-on: #13
2024-04-22 22:16:42 -07:00
a22c6c16be Merge pull request 'chore(deps): update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.13.0' (#14) from renovate/org.apache.maven.plugins-maven-compiler-plugin-3.x into master
Reviewed-on: #14
2024-04-22 22:16:38 -07:00
4b55674a98 Fallback to default icon for any errors whilst getting a server's favicon
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m41s
2024-04-23 01:09:39 -04:00
Renovate Bot
b359200a18 chore(deps): update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.13.0 2024-04-23 05:03:09 +00:00
Renovate Bot
9ec0c95e6d chore(deps): update dependency org.apache.maven.plugins:maven-shade-plugin to v3.5.2 2024-04-23 05:03:07 +00:00
8456829c30 Add README.md 2024-04-23 01:00:35 -04:00
cc67bfd054 Add demo Spigot plugin 2024-04-23 00:58:35 -04:00
2261bde6cb DNS records shouldnt be NonNull
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m45s
2024-04-22 23:44:19 -04:00
52ff61c554 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m34s
2024-04-22 23:14:34 -04:00
253c889b90 ignore default license 2024-04-22 23:14:26 -04:00
549e46c978 Update .gitea/workflows/deploy-api.yml
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 1m35s
2024-04-22 20:12:37 -07:00
023395ea69 Merge remote-tracking branch 'origin/master'
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Has been cancelled
2024-04-22 23:12:11 -04:00
90aea4a9cc yes 2024-04-22 23:12:02 -04:00
8f297aef26 Update .gitea/workflows/deploy-api.yml
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 22s
2024-04-22 20:10:50 -07:00
7b602a0f21 Fix test errs
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 26s
2024-04-22 23:07:46 -04:00
fb14eef179 Fix test check?
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 1m32s
2024-04-22 23:01:31 -04:00
81886fa097 Don't download MaxMind dbs in tests
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 23s
2024-04-22 22:59:04 -04:00
f3a57dc8d9 Geo location lookup
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 46s
2024-04-22 22:47:49 -04:00
1392d82480 Cleanup tasks 2024-04-22 21:48:31 -04:00
02695ce7a2 Add demo server
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m22s
2024-04-22 21:17:26 -04:00
5c32804114 Re-enable server cache
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m45s
2024-04-22 20:57:56 -04:00
1a4929d8b5 Identify server software if querying is enabled 2024-04-22 20:57:33 -04:00
c689434ec4 Add Java server querying! (:
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Has been cancelled
2024-04-22 20:49:35 -04:00
345e1532a4 Prevent Java versions from including unknown
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m44s
2024-04-22 15:36:45 -04:00
4d575fdf2e Add another Mojang server 2024-04-22 15:35:51 -04:00
867b9b263a docs view on git tooltip
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m6s
2024-04-22 02:30:42 -04:00
491295f5bd Stop using vanilla html <img />
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m8s
2024-04-22 02:27:13 -04:00
2eb9286dcf Run the footer on the client
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m8s
2024-04-22 02:06:05 -04:00
2ef6f6e4e2 Add copyright to the footer 2024-04-22 02:05:33 -04:00
eda3045cf4 Mojang page tooltips 2024-04-22 01:47:03 -04:00
8b31904095 Add feedback to search buttons
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m7s
2024-04-22 01:40:03 -04:00
58a97708fd Tooltips in a few more places
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 30s
2024-04-22 01:12:56 -04:00
3c912a4591 Add recommendation tooltips 2024-04-22 01:04:06 -04:00
f021eb4809 Add simple tooltip component 2024-04-22 01:03:52 -04:00
5d2136ce5a Disallow label selection 2024-04-22 00:49:20 -04:00
a33c1681bd Add server recommendations
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m25s
2024-04-22 00:46:47 -04:00
e50e6553c8 Fix the position of the server lookup form
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m32s
2024-04-22 00:04:47 -04:00
097a5d74fe Do some refactoring
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m10s
2024-04-21 23:58:56 -04:00
fb0b82edd3 Add code dialogs 2024-04-21 23:55:38 -04:00
060e991741 Unused code 2024-04-21 23:16:05 -04:00
8642740f90 Fix imports, and add a code highlighter component 2024-04-21 23:11:15 -04:00
17094d7cec Update doc titles 2024-04-21 22:55:15 -04:00
fe34001790 Custom doc embeds
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m8s
2024-04-21 22:24:42 -04:00
8ea4c20137 Make docs responsive on mobile
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 11m36s
2024-04-21 22:03:53 -04:00
21354da41e Slight animation change to the docs search dialog 2024-04-21 21:27:44 -04:00
183c61cfdc Show lang icons
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m7s
2024-04-21 21:23:26 -04:00
538bfc4d75 Make the lang text darker 2024-04-21 21:11:18 -04:00
132f45eec8 Show lang used in code blocks
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m57s
2024-04-21 21:07:59 -04:00
577851c69b Add code syntax highlighting
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m21s
2024-04-21 20:55:42 -04:00
986f3b8f02 Cleanup 2024-04-21 20:26:29 -04:00
b8cecee57e Make the KBD look better
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m2s
2024-04-21 20:20:30 -04:00
c469b2d5cf Add a keybind for the docs search dialog
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m3s
2024-04-21 20:15:41 -04:00
280df436d2 Fix sidebar padding 2024-04-21 20:04:26 -04:00
6963085295 Update search ui
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m52s
2024-04-21 19:51:30 -04:00
349747d555 Add searching to the docs
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 2m52s
2024-04-21 18:14:16 -04:00
0413e0f448 Indent lists
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m3s
2024-04-21 14:46:49 -04:00
6f36c8bc4e Custom error page
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m1s
2024-04-21 13:51:31 -04:00
335ed10a53 Fix the docs sidebar active link status not properly working
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m1s
2024-04-21 13:45:26 -04:00
3ea7e8c70e Space out markdown headings more
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m2s
2024-04-21 13:41:22 -04:00
e1e02ae0f4 Docs sidebar
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m2s
2024-04-21 13:35:12 -04:00
8f6f5094bc Make docs more responsive 2024-04-21 13:31:12 -04:00
75fabe648e Make the nav logo slightly smaller
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m3s
2024-04-21 12:40:30 -04:00
ee6a20054c Make the footer more mobile responsive
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m3s
2024-04-21 12:35:37 -04:00
e8000a1fbc Make the nav and hero more mobile responsive
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m1s
2024-04-21 12:33:30 -04:00
e7bdfe1f65 Make the docs page more responsive
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m12s
2024-04-21 12:18:34 -04:00
0f77b9aa68 View on git button for docs 2024-04-21 12:14:03 -04:00
9769149689 UID is used
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m0s
2024-04-21 11:10:30 -04:00
a31dcc7618 Dockerfile yarn -> bun
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 50s
2024-04-21 11:08:52 -04:00
42f9e7090f fix?
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m58s
2024-04-21 02:20:07 -04:00
f65cc4e804 fix?
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 3m5s
2024-04-21 02:03:43 -04:00
0a4f00866d pls
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m52s
2024-04-21 01:55:55 -04:00
47d2982666 use vanilla table
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m4s
2024-04-21 01:46:34 -04:00
14122b0267 Update home.md
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m5s
2024-04-21 01:44:06 -04:00
53c19d8e48 will this fix table parsing?
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m51s
2024-04-21 01:38:36 -04:00
0dac73bff4 add lock
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m49s
2024-04-21 01:27:23 -04:00
62d89def61 fix table parse err?
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Has been cancelled
2024-04-21 01:26:56 -04:00
4cd88be2ad now?
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m3s
2024-04-21 01:19:15 -04:00
4372832196 Force static doc pages
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m5s
2024-04-21 01:17:52 -04:00
ec332842e7 fix?
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m3s
2024-04-21 01:16:15 -04:00
0ccfd5ea22 Cleanup the table
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m2s
2024-04-21 01:14:10 -04:00
0d77a2e219 undefined check for table
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m3s
2024-04-21 01:10:17 -04:00
e1acb73805 fix spacing
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m5s
2024-04-21 01:07:27 -04:00
e9f30885b0 Begin on the docs page design
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 3m15s
2024-04-21 01:01:52 -04:00
534e67b6f0 Fix markdown highlighting 2024-04-20 21:42:31 -04:00
5e67ce1f34 Make markdown work recursively
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 2m53s
2024-04-20 18:07:13 -04:00
a665d5d0ba Update ping icon size
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 59s
2024-04-20 15:27:35 -04:00
74392b6288 .
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 11s
2024-04-20 15:20:32 -04:00
7afd38bcc2 Update server page theme color
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Has been cancelled
2024-04-20 15:20:21 -04:00
9e60a7d8c5 Update server page embed color
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m49s
2024-04-20 14:26:29 -04:00
b1d2e895ee Cleanup server page 2024-04-20 14:19:26 -04:00
c286ae4a21 Cleanup player page 2024-04-20 14:18:52 -04:00
b26718df8b Player embed colors
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m5s
2024-04-20 13:59:40 -04:00
bc769651c3 oops, forgot the font
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 59s
2024-04-20 13:44:22 -04:00
f308456a4a Update media
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 19s
2024-04-20 13:43:14 -04:00
8bda711215 Update the style of the server result 2024-04-20 13:34:47 -04:00
9f2dc1d4ed Update Minecraft font 2024-04-20 13:04:34 -04:00
1a96d6a522 Update server background image
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m10s
2024-04-20 12:46:03 -04:00
791f88e11b oops lol
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 36s
2024-04-20 01:58:13 -04:00
e4176a4dbe compile docs?
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 57s
2024-04-20 01:55:54 -04:00
e4d05f5104 .md file support
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 33s
2024-04-20 01:47:53 -04:00
02b1eea17b Update markdown docs path
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Has been cancelled
2024-04-20 01:46:04 -04:00
e92622c023 sharp! 2024-04-20 01:45:55 -04:00
a8cf72e981 Docs page
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m40s
2024-04-20 01:33:33 -04:00
ab05ad8a3e Use the Minecraft font for the server response 2024-04-19 18:25:53 -04:00
9a78a6988b Disablt auto complete in field inputs 2024-04-19 18:18:27 -04:00
98adb3d22d oops, fix build err
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m0s
2024-04-19 11:36:31 -04:00
d46fd443d4 Embed color test
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 29s
2024-04-19 11:33:29 -04:00
4af63c65a0 Add a pull request template 2024-04-19 11:09:17 -04:00
8cdd990938 Move issue template to root dir
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m4s
2024-04-19 11:03:17 -04:00
408ad7d7b7 Add issue template 2024-04-19 10:57:19 -04:00
c6f118d629 yes
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m1s
2024-04-19 10:55:08 -04:00
1244b6d142 Cleanup and center the links in the navbar
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Has been cancelled
2024-04-19 10:54:56 -04:00
4bccec1f5d Update API/src/main/resources/public/index.html
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 27s
2024-04-19 07:15:03 -07:00
2f1a16f3f2 Use the same theme color as the frontend
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Has been cancelled
2024-04-19 07:14:54 -07:00
c11383107c Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
Some checks failed
Publish JS SDK / docker (push) Failing after 20s
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 35s
2024-04-19 10:11:33 -04:00
f5c593a95f Update JS SDK workflow 2024-04-19 10:11:25 -04:00
b3db5c5081 Update Frontend workflow 2024-04-19 10:08:55 -04:00
c758314d97 Update API/README.md 2024-04-19 07:06:28 -07:00
0d9c5cbfc6 Update API/README.md 2024-04-19 07:06:15 -07:00
2edbacd55c Update API workflow
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m45s
2024-04-19 10:03:12 -04:00
3194acfc83 Merge pull request 'fix(deps): update dependency lucide-react to ^0.372.0' (#11) from renovate/lucide-react-0.x into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m38s
Reviewed-on: #11
2024-04-19 06:55:05 -07:00
Renovate Bot
0d5e185ffd fix(deps): update dependency lucide-react to ^0.372.0 2024-04-19 09:02:21 +00:00
6fa05990c6 Update some errors
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m0s
2024-04-18 20:42:35 -04:00
bf1c1af0e5 Fix not being able to click the link on the docs page
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m0s
2024-04-18 20:31:47 -04:00
e3e8df6953 Cant click the server status 2024-04-18 20:31:00 -04:00
507ae5c413 Center the footer disclaimer
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 59s
2024-04-18 20:29:58 -04:00
2aed687713 oops, add missing assets
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m0s
2024-04-18 20:19:50 -04:00
f7dea28738 Open Mojang status endpoints in a new URL 2024-04-18 20:18:33 -04:00
b08cb6efb6 license
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m5s
2024-04-18 20:14:13 -04:00
f1cd72e8b3 wow a footer!
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Has been cancelled
2024-04-18 20:12:25 -04:00
ab56798ae9 Merge pull request 'fix(deps): update dependency org.springframework.boot:spring-boot-starter-parent to v3.2.5' (#10) from renovate/spring-boot into master
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m41s
Reviewed-on: #10
2024-04-18 13:45:38 -07:00
5e086c17b8 Make GH stars a client component
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m3s
2024-04-18 16:44:25 -04:00
817493bfd4 Fix raw json positioning for the player result 2024-04-18 16:34:42 -04:00
Renovate Bot
28e28c8ade fix(deps): update dependency org.springframework.boot:spring-boot-starter-parent to v3.2.5 2024-04-18 19:02:05 +00:00
c4d915095c x.x
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m14s
2024-04-18 14:06:19 -04:00
a57e95806e Fix card spacing
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 45s
2024-04-18 14:04:54 -04:00
618d523ca2 Make the /mojang page dynamic
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 37s
2024-04-18 14:02:42 -04:00
bacdc9319f Add the Mojang status page
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m58s
2024-04-18 13:58:21 -04:00
cb0280f817 Update the JS SDK to support the new layout of the /mojang/status endpoint
All checks were successful
Publish JS SDK / docker (push) Successful in 21s
2024-04-18 13:18:00 -04:00
fd16430307 Update the return json of /mojang/status in the API
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 2m14s
2024-04-18 13:09:23 -04:00
979018b508 .
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m9s
2024-04-18 12:51:45 -04:00
6e21847c3b Make the hero component spacing smaller 2024-04-18 12:42:47 -04:00
e104bc8728 Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m17s
2024-04-18 12:34:50 -04:00
e38b9ff539 Add a background to the hero 2024-04-18 12:34:47 -04:00
038766b8b1 Merge pull request 'fix(deps): update dependency lucide-react to ^0.371.0' (#9) from renovate/lucide-react-0.x into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m41s
Reviewed-on: #9
2024-04-18 08:47:52 -07:00
Renovate Bot
9332bbde13 fix(deps): update dependency lucide-react to ^0.371.0 2024-04-18 08:03:11 +00:00
727a8f801f Update Frontend/config.json
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m9s
2024-04-17 22:04:56 -07:00
4da56be833 Merge pull request 'chore(deps): update nextjs monorepo to v14.2.2' (#8) from renovate/nextjs-monorepo into master
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Has been cancelled
Reviewed-on: #8
2024-04-17 22:02:28 -07:00
Renovate Bot
4bfeb88989 chore(deps): update nextjs monorepo to v14.2.2 2024-04-18 05:02:07 +00:00
8bcc5aa352 Player and Server page embeds
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m11s
2024-04-18 00:38:38 -04:00
46a0869b85 Format server player count in embed with commas
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m11s
2024-04-18 00:36:50 -04:00
cb75a3a4c8 make toasts look better (:
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 3m2s
2024-04-18 00:31:45 -04:00
bf3a09afd4 oops
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m19s
2024-04-18 00:12:13 -04:00
4b5c8d1d89 Don't move toasts to the top for smaller screens
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m24s
2024-04-18 00:09:40 -04:00
d1d78ef71b Add toasts to copy buttons
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m41s
2024-04-18 00:04:33 -04:00
7c9ba6d151 Fix naming for server platform in embeds
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m10s
2024-04-17 23:52:48 -04:00
a20b8008f4 Server embeds
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m12s
2024-04-17 23:47:42 -04:00
4494bd74ec fix centering 2024-04-17 23:40:38 -04:00
56563802be server route!
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m59s
2024-04-17 23:34:13 -04:00
367c974cb3 Fix copy buttons in context menu
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m10s
2024-04-17 20:54:15 -04:00
b52f9d1d38 Fix build error
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m28s
2024-04-17 20:47:45 -04:00
a1dfa6b6fa Updates
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 1m55s
2024-04-17 20:37:49 -04:00
9905673b1e Fix badge color
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m8s
2024-04-17 19:44:59 -04:00
9dbe9632c0 Raw Json button
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m10s
2024-04-17 19:43:35 -04:00
69ce0fc469 Update the docs page
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m8s
2024-04-17 19:19:52 -04:00
c9f15011af Make the 404 page responsive
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m13s
2024-04-17 19:16:49 -04:00
cb6e5dc794 Disable the footer for now, it isn't done 2024-04-17 19:14:40 -04:00
0610a6acfa warning fixes 2024-04-17 19:14:10 -04:00
6790083006 oops forgot these lol 2024-04-17 19:06:02 -04:00
b560341068 Changes 2024-04-17 19:04:15 -04:00
6a9ec1fe9f Show GitHub star button on smaller devices
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m47s
2024-04-17 18:04:32 -04:00
2f419c1754 Make the nav and landing page better on mobile
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m5s
2024-04-17 17:50:05 -04:00
fa2a2dd8d8 Stats counter
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m26s
2024-04-17 17:34:07 -04:00
eadcebab4d Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC 2024-04-17 13:48:11 -04:00
4dfc081305 Fix the Get Started button in the hero 2024-04-17 13:45:12 -04:00
7dd5adfffb Merge pull request 'fix(deps): update dependency lucide-react to ^0.370.0' (#7) from renovate/lucide-react-0.x into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m41s
Reviewed-on: #7
2024-04-17 10:30:51 -07:00
Renovate Bot
2de5aa5581 fix(deps): update dependency lucide-react to ^0.370.0 2024-04-17 15:02:24 +00:00
132b3e0315 Make the /player page responsive
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m9s
2024-04-17 10:55:10 -04:00
6d728dcb10 Add CONTRIBUTING.md 2024-04-17 10:19:01 -04:00
87d6e0ab3b Fix the navbar being off center
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m7s
2024-04-17 09:43:14 -04:00
eff8af532a Fix spacing for the star on GH button 2024-04-17 09:35:22 -04:00
05131b614d docs metadata 2024-04-17 09:33:02 -04:00
b6d59b9598 Merge pull request 'chore(deps): update dependency eslint to v9' (#6) from renovate/eslint-9.x into master
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 2m48s
Reviewed-on: #6
2024-04-17 06:23:11 -07:00
Renovate Bot
ca343f22c6 chore(deps): update dependency eslint to v9 2024-04-17 04:02:32 +00:00
ab495cd471 embed color
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 57s
2024-04-16 23:31:15 -04:00
ae4ece4ee5 does this work?
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 58s
2024-04-16 23:28:12 -04:00
54c116b85c Embed updates
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 57s
2024-04-16 23:25:41 -04:00
d5187aff6c robots 2024-04-16 23:24:02 -04:00
f4f51b8912 Fix the GitHub star button
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 56s
2024-04-16 23:15:09 -04:00
51b8d763c1 embed test 2024-04-16 23:15:00 -04:00
45f38396c6 Update player search form
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 3m6s
2024-04-16 23:10:19 -04:00
b89fd74515 Update for new domain 2024-04-16 22:59:16 -04:00
490ba046ef Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 1m25s
2024-04-16 20:53:07 -04:00
03df9aabdf Fix URL on the frontend 2024-04-16 20:52:59 -04:00
9b87a84215 Update .gitea/workflows/deploy-frontend.yml
All checks were successful
Deploy Frontend / docker (17, 3.8.5) (push) Successful in 3m15s
2024-04-16 17:24:57 -07:00
21f31c0f1d updates to the frontend
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 18s
2024-04-16 20:23:09 -04:00
0216662767 Updates to the frontend 2024-04-16 20:22:57 -04:00
c7d0f31852 Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
All checks were successful
Publish JS SDK / docker (push) Successful in 23s
2024-04-16 20:04:44 -04:00
198d8cf1c1 fix npm publish error for the JS SDK 2024-04-16 20:04:31 -04:00
c707b0d3ac Update API deploy workflow
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m37s
2024-04-16 16:55:40 -07:00
a503e814f9 Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
Some checks failed
Publish JS SDK / docker (push) Failing after 20s
2024-04-16 19:53:40 -04:00
bfd6a93229 Version bump 2024-04-16 19:53:28 -04:00
3431e7fe03 Change the endpoint URL in the JS SDK 2024-04-16 19:53:08 -04:00
0972e6d102 Update API/README.md
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 1m39s
2024-04-16 16:46:41 -07:00
83b2a96490 show players! 2024-04-16 18:27:56 -04:00
6dbbc50610 Add BODY_FLAT skin part to the JS SDK
Some checks failed
Publish JS SDK / docker (push) Failing after 27s
2024-04-16 18:11:56 -04:00
0fb320c076 Fix enums in the JS SDK
Some checks failed
Publish JS SDK / docker (push) Failing after 17s
2024-04-16 17:17:20 -04:00
3beccf01c8 Update src
Some checks failed
Publish JS SDK / docker (push) Failing after 16s
2024-04-16 16:56:38 -04:00
c6259e3c8d code! 2024-04-16 16:40:25 -04:00
3588b5f93c Remove extra slash in web requests 2024-04-16 14:17:34 -04:00
a90299fae7 JS SDK ver bump 2024-04-16 14:17:18 -04:00
7fe1b51973 Make mobile responsiveness better for the landing page 2024-04-16 10:54:30 -04:00
abc35e4b21 Make the hero mobile responsive 2024-04-16 10:52:55 -04:00
86f71ad0c1 Fix the nav overlaying other elements 2024-04-16 10:52:44 -04:00
10b12a60da Move featured items to the config, and make it more responsive 2024-04-16 10:48:32 -04:00
8195239251 Open GitHub link in new page 2024-04-16 10:26:19 -04:00
6dca925ee4 Not found page 2024-04-16 10:26:01 -04:00
31806a6d58 Fix layout error 2024-04-16 10:25:50 -04:00
8f2cd92e62 update creeper 2024-04-16 10:25:37 -04:00
c367fc7474 Docs page 2024-04-16 10:13:20 -04:00
b5251cbf59 creeper! 2024-04-16 10:13:13 -04:00
7465ba6175 More content 2024-04-16 09:59:20 -04:00
750c4cbc63 wtf was i doing lmao 2024-04-16 08:42:14 -04:00
3d48b9fd26 Fix warning with MC button 2024-04-16 07:48:53 -04:00
ec393c0f64 Scrollbar 2024-04-16 07:48:42 -04:00
7958fb3c21 More landing page content 2024-04-15 23:24:50 -04:00
061b7df8b1 Make the navbar responsive 2024-04-15 22:53:23 -04:00
18c298376a Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC 2024-04-15 22:32:00 -04:00
d29004f673 Get a better style down 2024-04-15 22:31:55 -04:00
0072dc0362 Frontend base 2024-04-15 18:00:47 -04:00
158b3ec69e Update .gitea/workflows/publish-js-sdk.yml
Some checks failed
Publish JS SDK / docker (push) Failing after 16s
2024-04-15 13:32:01 -07:00
12cf444578 Add Frontend
Some checks failed
Publish JS SDK / docker (push) Failing after 17s
2024-04-15 16:16:08 -04:00
01a55e63b4 Bump version
All checks were successful
Publish JS SDK / docker (push) Successful in 19s
2024-04-15 16:04:35 -04:00
90e170b577 Add HttpMethod enum
Some checks failed
Publish JS SDK / docker (push) Failing after 19s
2024-04-15 15:46:53 -04:00
75316d5f6b Fix Mojang tests
All checks were successful
Publish JS SDK / docker (push) Successful in 21s
2024-04-15 11:25:42 -04:00
9cf2f10ee9 Export enums
Some checks failed
Publish JS SDK / docker (push) Failing after 19s
2024-04-15 11:24:03 -04:00
77bff8067e Export server platform
All checks were successful
Publish JS SDK / docker (push) Successful in 32s
2024-04-15 11:10:05 -04:00
9be372e3e0 JS SDK version bump 2024-04-15 11:08:10 -04:00
053bfedfa1 JS SDK version bump
All checks were successful
Publish JS SDK / docker (push) Successful in 48s
2024-04-15 11:06:00 -04:00
fc382fad27 Fix skin part not being exported
Some checks failed
Publish JS SDK / docker (push) Has been cancelled
2024-04-15 11:05:31 -04:00
bb81be1e28 Fix tests not running
All checks were successful
Publish JS SDK / docker (push) Successful in 26s
2024-04-15 10:38:35 -04:00
3b9cf6f818 Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
Some checks failed
Publish JS SDK / docker (push) Failing after 18s
2024-04-15 10:32:56 -04:00
cacf1dc4cc oops, missed some files 2024-04-15 10:32:53 -04:00
a51528616d Fix the build not including all types 2024-04-15 10:32:44 -04:00
9f1ce626df JS SDK version bump 2024-04-15 10:04:02 -04:00
1277644bcb Update README.md 2024-04-15 06:50:13 -07:00
ffe005d995 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy API / docker (17, 3.8.5) (push) Successful in 1m34s
2024-04-15 09:48:30 -04:00
3365d01c8e Add README.md to API 2024-04-15 09:48:22 -04:00
2214636c47 Update JS-SDK/README.md
Some checks failed
Publish JS SDK / docker (push) Failing after 22s
2024-04-15 06:45:57 -07:00
74a3305ddd Add server and Mojang tests
Some checks failed
Publish JS SDK / docker (push) Failing after 19s
2024-04-15 09:43:40 -04:00
3171346d4b Fix #isMojangBlocked not working 2024-04-15 09:40:19 -04:00
fbbb4e1483 Do proper tests
All checks were successful
Publish JS SDK / docker (push) Successful in 19s
2024-04-15 09:29:39 -04:00
834ae9df44 Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
Some checks failed
Publish JS SDK / docker (push) Failing after 21s
2024-04-15 09:12:53 -04:00
3d409dc661 Update imports to use paths 2024-04-15 09:12:41 -04:00
cad22ad30a Update README.md 2024-04-15 05:56:53 -07:00
9139972955 Update README.md 2024-04-15 05:56:06 -07:00
6322af94bb Fix build error?
All checks were successful
Publish JS SDK / docker (push) Successful in 30s
2024-04-15 08:52:10 -04:00
d1365ae261 Cleanup
Some checks failed
Publish JS SDK / docker (push) Failing after 32s
2024-04-15 08:49:09 -04:00
3e42ed414a Add #getSkinPart and #getJavaServerFavicon 2024-04-15 08:48:57 -04:00
d834badb78 Use an enum for server platforms 2024-04-15 07:56:05 -04:00
baac1391ee Merge remote-tracking branch 'origin/master' 2024-04-15 07:52:47 -04:00
a218dc7ec3 oops, wasn't properly resolving the array buffer in web requests 2024-04-15 07:51:20 -04:00
1eb4a9ec0e Fix server types 2024-04-15 07:49:00 -04:00
bf9fa6582f JS minor version bump 2024-04-15 07:47:07 -04:00
b07d8c0888 Add better support to web requests 2024-04-15 07:46:45 -04:00
aa3813cb34 Update README.md 2024-04-15 04:30:24 -07:00
cf345c3410 Update workflow file name
Some checks failed
Publish JS SDK / docker (push) Failing after 18s
2024-04-15 07:28:47 -04:00
76201edd92 Lib -> JS-SDK
Some checks failed
Publish JS SDK / docker (push) Failing after 18s
2024-04-15 07:20:56 -04:00
1c1ea04cf0 Lib cleanup
Some checks failed
Deploy Lib / docker (push) Failing after 19s
2024-04-15 07:16:06 -04:00
49a6e9b7b0 Update lib README.md
Some checks failed
Deploy Lib / docker (push) Failing after 19s
2024-04-14 17:53:50 -04:00
4c614525b0 Update lib README.md
Some checks failed
Deploy Lib / docker (push) Has been cancelled
2024-04-14 17:52:45 -04:00
6c98d1e862 Update package name
All checks were successful
Deploy Lib / docker (push) Successful in 52s
Deploy API / docker (17, 3.8.5) (push) Successful in 54s
2024-04-14 17:31:27 -04:00
b087666afb Cleanup the deploy api workflow 2024-04-14 17:29:45 -04:00
67d6df8916 finally omg
Some checks failed
Deploy Lib / docker (push) Failing after 54s
2024-04-14 17:27:27 -04:00
dfe99c1acf fix auth?
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 53s
2024-04-14 17:25:38 -04:00
4b3e478329 ???????
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 54s
2024-04-14 17:23:52 -04:00
42cfab11f2 test
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 53s
2024-04-14 17:21:47 -04:00
92a609ece5 Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Has been cancelled
2024-04-14 17:18:42 -04:00
6a561a8000 will this work? 2024-04-14 17:18:40 -04:00
39556c2a8d Update README.md 2024-04-14 14:15:06 -07:00
96cbbc1632 Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 1m7s
2024-04-14 17:12:41 -04:00
f4df437fc7 debug x.x 2024-04-14 17:12:39 -04:00
57bfc1b9da Update README.md 2024-04-14 14:09:22 -07:00
07ab78fd08 specify custom registry
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 38s
2024-04-14 17:07:47 -04:00
f4b8d5708f diff env var
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 46s
2024-04-14 17:04:04 -04:00
6c4ee55b95 Preserve whitespace in HTML
All checks were successful
Deploy API / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m31s
2024-04-14 17:01:27 -04:00
d1b33d3d6e Ensure public access
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 47s
2024-04-14 16:57:29 -04:00
549bbc864e will it publish now???
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 1m0s
2024-04-14 16:54:36 -04:00
d49c9b20e4 Update deploy lib workflow
Some checks failed
Deploy API / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m0s
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 1m4s
2024-04-14 16:51:19 -04:00
7ad3c0f1f3 Update deploy api workflow 2024-04-14 16:49:34 -04:00
e3b88ec32e why tf doesnt this cache
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 1m3s
2024-04-14 16:48:15 -04:00
4c0346ab3e don't setup bun each time
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 8s
2024-04-14 16:43:35 -04:00
13d0a344b9 Fix publish auth err?
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Has been cancelled
2024-04-14 16:42:43 -04:00
7ebeb39cc3 oops, fix build error
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 52s
2024-04-14 16:38:41 -04:00
acae5dbcd6 Disable tests, not done yet
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 1m31s
2024-04-14 16:35:55 -04:00
2d58908626 Install depends
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 1m28s
2024-04-14 16:33:44 -04:00
50b5c8ba4b Cleanup the lib
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 1m29s
2024-04-14 16:29:29 -04:00
6e096554d8 fixed workflow not working
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0) (push) Failing after 3m2s
2024-04-14 16:22:40 -04:00
71cbf6ef1b now? 2024-04-14 16:22:08 -04:00
ae4079b6d8 Update deploy lib workflow 2024-04-14 16:21:33 -04:00
7e98b68db0 dont need this 2024-04-14 16:20:32 -04:00
e8b365b6f0 Run tests 2024-04-14 16:19:18 -04:00
d45b26fe09 Add deploy-lib workflow
Some checks failed
Deploy Lib / docker (ubuntu-latest, 2.44.0, 20.x) (push) Has been cancelled
2024-04-14 16:15:47 -04:00
13c0a76e97 DNS Record types 2024-04-14 16:10:45 -04:00
fe00e7d50b Add #getMojangServerStatus 2024-04-14 16:06:42 -04:00
18293cc579 Add #isMojangBlocked 2024-04-14 15:56:45 -04:00
499663aa64 Add Minecraft server lookup support to the lib 2024-04-14 15:53:36 -04:00
abccde5896 Update Player type 2024-04-14 14:35:55 -04:00
1968f835fd functional lib 2024-04-14 14:03:02 -04:00
9f0d85cf5b oops ignore this 2024-04-14 13:35:22 -04:00
c0ce86faf6 oops, ignore this 2024-04-14 13:35:14 -04:00
eabea2546b Merge branch 'master' of https://git.rainnny.club/Rainnny/RESTfulMC 2024-04-14 13:34:37 -04:00
9751a21c9f Make basic Lib 2024-04-14 13:34:34 -04:00
4777d7d5eb Fix a bug with some Bedrock servers missing data in the token
All checks were successful
Deploy API / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m40s
2024-04-14 09:23:17 -07:00
4721b46ce9 Update API/Dockerfile
All checks were successful
Deploy API / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m39s
2024-04-14 09:16:09 -07:00
27c83a55ba Update API/.gitignore
All checks were successful
Deploy API / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m31s
2024-04-14 08:54:33 -07:00
7ab210edb7 Move to monorepo layout
All checks were successful
Deploy API / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 26s
2024-04-14 11:26:54 -04:00
348f7fa1eb Fix failed unit tests
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m34s
2024-04-13 13:27:47 -04:00
3f1290fd07 Change response for Minecraft servers
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 25s
2024-04-13 13:26:03 -04:00
d6a21fd5a3 Fix internal server err
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m42s
2024-04-13 13:05:46 -04:00
9c4a39641d Run #fetchMojangServerStatuses in parallel 2024-04-13 13:04:43 -04:00
b800badcc3 Add Unit testing for the Mojang route
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m33s
2024-04-13 11:51:52 -04:00
6c6b9349f2 Add Mojang status route
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m35s
2024-04-13 11:39:51 -04:00
6d112ac658 Fix server tests failing
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m30s
2024-04-12 02:59:19 -04:00
f01c208a9b Remove unused depends
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 31s
2024-04-12 02:56:47 -04:00
5099ad9dfe Add a legacy field to players
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m26s
2024-04-11 09:21:16 -04:00
a03bac8f1d Allow for Java user profiles to be signed
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m27s
2024-04-11 09:09:25 -04:00
655ee50a21 Cache skin image data 2024-04-11 08:56:42 -04:00
63121afe32 CORS Config
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m26s
2024-04-11 08:16:12 -04:00
6993fe6681 Cleanup
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m30s
2024-04-11 07:22:24 -04:00
d8962cee98 oops fix err
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m23s
2024-04-11 05:32:43 -04:00
0890a151eb Cleanup 2024-04-11 05:32:31 -04:00
0de598b205 Add the body part renderer 2024-04-11 05:17:00 -04:00
4f24b8eee5 Cleanup 2024-04-11 04:48:12 -04:00
dbeeb77fc8 Cleanup
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m23s
2024-04-11 04:41:00 -04:00
43f2ba041d Show all skin parts in the player skin
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
2024-04-11 04:40:40 -04:00
5f37b6b768 Take overlay renders into account for caching skin parts
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
2024-04-11 04:38:59 -04:00
16e23e82f9 Allow for custom skin renderers
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m41s
2024-04-11 04:36:37 -04:00
2f7b9f6b10 Add base for 3D skin heads!!!
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m35s
2024-04-11 03:04:50 -04:00
16a42d1af3 Add profile properties to Player 2024-04-11 02:20:18 -04:00
160fed45e8 Tuple -> SkinProperties 2024-04-10 11:18:42 -04:00
27beec9dc7 Fix deserialization of DNS records not working
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m39s
2024-04-10 10:29:32 -04:00
0cb6d3f028 Show DNS records in MC server responses
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 22s
2024-04-10 10:21:27 -04:00
3a098433e0 Remove duplicated code 2024-04-10 09:46:19 -04:00
2df4a7978c Small fixes
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m25s
2024-04-10 09:40:19 -04:00
20adfaeaad Add more data to servers
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m30s
2024-04-10 09:27:09 -04:00
501d315c96 Add support for Minecraft 1.7.10 2024-04-10 09:21:42 -04:00
592bca234b Change default Jackson behaviour to not serialize nulls
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m23s
2024-04-10 08:23:38 -04:00
d2a54ac828 Move to Jackson for default json
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m25s
2024-04-10 08:20:57 -04:00
d11f87e36e Update Swagger logo 2024-04-10 07:43:46 -04:00
cd147684e5 Merge pull request 'fix(deps): update dependency org.springdoc:springdoc-openapi-starter-webmvc-ui to v2.5.0' (#5) from renovate/org.springdoc-springdoc-openapi-starter-webmvc-ui-2.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m28s
Reviewed-on: #5
2024-04-10 04:42:19 -07:00
33d1f9669a Ensure the skin part cache uses lowercase
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m27s
2024-04-10 07:40:23 -04:00
81029a57a1 Add caching for skin part textures
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 22s
2024-04-10 07:37:58 -04:00
c9426d11af Fix servers not caching
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m38s
2024-04-10 07:24:24 -04:00
af9df6571c Fix broken server port test yes
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m27s
2024-04-10 07:18:23 -04:00
69c0cd3179 Update Swagger docs
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 23s
2024-04-10 07:11:18 -04:00
9725237885 Change how ports are defined in requests 2024-04-10 07:08:42 -04:00
a4a3486341 Swagger docs
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m53s
2024-04-10 06:12:05 -04:00
Renovate Bot
f1e6dbf08f fix(deps): update dependency org.springdoc:springdoc-openapi-starter-webmvc-ui to v2.5.0 2024-04-10 10:02:19 +00:00
b6ebe767ee Re-enable ensureBedrockServerLookupSuccess
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m48s
2024-04-10 05:59:33 -04:00
ca794e288c Fix Bedrock servers not always pinging 2024-04-10 05:59:03 -04:00
657c7196dd temp disable bedrock server test
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 20s
2024-04-10 05:49:22 -04:00
59f6c51717 Some cleanup and refactoring
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 20s
2024-04-10 05:45:38 -04:00
ca0624995d Update Swagger servers
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 22s
2024-04-10 05:05:02 -04:00
16e15a3dd3 Update README.md 2024-04-10 04:55:39 -04:00
0831b509e1 Implement SwaggerAPI
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m26s
2024-04-10 04:51:18 -04:00
931e6c2de1 Add ServerControllerTests#ensureUnknownPort
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m18s
2024-04-10 04:27:39 -04:00
66e84b9c08 Cleanup 2024-04-10 04:20:59 -04:00
1d5aa930f0 Added the ability to specify a port in server and server favicon requests 2024-04-10 04:20:39 -04:00
9e99db3aa3 Run JUnit tests in the deploy workflow
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m53s
2024-04-10 03:23:41 -04:00
546a187f52 Remove InvalidMinecraftServerPlatformException 2024-04-10 02:25:54 -04:00
ad51d4d357 Fix server icon route producing the wrong file name
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 9s
2024-04-09 01:31:48 -04:00
82162b5f63 Fix server favicon URLs being incorrect
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 59s
2024-04-09 01:23:55 -04:00
f2601f6971 Ignore .gitignore file in deploy workflow
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 58s
2024-04-09 01:20:48 -04:00
decb38e6dd Update .gitignore 2024-04-09 01:13:00 -04:00
46d0e80454 Update docs 2024-04-09 01:02:55 -04:00
ec69120a26 Handle invalid size params for player skin parts
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m5s
2024-04-09 00:51:01 -04:00
32c93b5e58 Fix Bedrock server edition parsing
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m1s
2024-04-09 00:36:01 -04:00
936a1c47eb Update copyright
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m2s
2024-04-09 00:27:03 -04:00
69393472c1 Add Bedrock server tests
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m7s
2024-04-09 00:22:02 -04:00
725cd00163 Update Postman collection 2024-04-09 00:15:24 -04:00
04a0fc8f0e Make the postman logo smaller 2024-04-09 00:05:27 -04:00
dea3227a05 update docs lol
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m3s
2024-04-08 23:52:39 -04:00
9250a6514a Update copyright
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m4s
2024-04-08 23:47:10 -04:00
6b1465e29f Fix server caching not taking platform into account
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m0s
2024-04-08 23:39:57 -04:00
7f19304b9e Update server icon route - only support Java edition
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m5s
2024-04-08 23:33:17 -04:00
e9ce888d06 Update server models 2024-04-08 23:32:54 -04:00
8cca0c2b51 Add Bedrock server pinger 2024-04-08 23:32:45 -04:00
e5342b5445 Add Bedrock packets 2024-04-08 23:31:48 -04:00
91f207448e Add MojangRateLimitException
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m3s
2024-04-08 04:07:18 -04:00
1ad27fc812 Add more Unit tests 2024-04-08 04:00:19 -04:00
ef36799979 Add more player unit tests 2024-04-08 03:49:01 -04:00
fb7bd3299f Fix invalid username error msg 2024-04-08 03:48:52 -04:00
77ba09d810 Whitelist some illegal usernames x.x 2024-04-08 03:37:53 -04:00
de0cd5ef8c Add username regex validation 2024-04-08 03:32:36 -04:00
712208aad8 Add favicon 2024-04-08 03:24:33 -04:00
4dd16da26a Update README.md 2024-04-08 03:22:17 -04:00
d5547c95b7 Add deploy workflow status to README.md 2024-04-08 03:20:38 -04:00
bf9555674d Skip unit testing for now
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Successful in 57s
2024-04-08 02:43:27 -04:00
38fa1fe4cb Update Dockerfile
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Failing after 47s
2024-04-08 02:29:41 -04:00
ecb8b31df1 yes
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Failing after 33s
2024-04-08 02:23:49 -04:00
5b80d41992 Add Redis DB logging
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Failing after 39s
2024-04-08 02:21:44 -04:00
e9e6999adc Change default app config
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Failing after 37s
2024-04-08 02:15:53 -04:00
9ab0b15869 test
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Failing after 39s
2024-04-08 02:14:30 -04:00
5a044df870 Merge remote-tracking branch 'origin/master'
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0) (push) Failing after 43s
2024-04-08 02:12:50 -04:00
c62917f0c7 yes? 2024-04-08 02:12:44 -04:00
8fc503617a Merge pull request 'chore(deps): update dependency com.github.codemonstur:embedded-redis to v1.4.3' (#3) from renovate/com.github.codemonstur-embedded-redis-1.x into master
Reviewed-on: #3
2024-04-07 23:06:49 -07:00
Renovate Bot
fb4ea67a77 chore(deps): update dependency com.github.codemonstur:embedded-redis to v1.4.3 2024-04-08 06:03:22 +00:00
305 changed files with 18236 additions and 36498 deletions

View 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 }}

View 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 }}

View File

@ -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 }}

View 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

View 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 }}

View File

@ -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

View File

@ -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
View 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)

View File

@ -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.20.0</version>
<scope>compile</scope>
</dependency>

View 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);
}
}

View 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 == ' ' ? "&nbsp;" : character); // Append the char...
}
return builder.toString();
}
}

View 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;
}
}

View 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;
}
}
}

View 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.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"));
}
}

View 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();
}
}

View 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;
}
}

View 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();
}
}
}

View File

@ -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;
}
}

View 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();
}
}

View 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
}
}

View 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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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()));
}
}

View File

@ -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;
}
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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()));
}
}

View 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.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);
}
}

View File

@ -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
}
}
}

View 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.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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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.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;
}
}

View File

@ -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;
}
}
}

View 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
}
};
}
}

View 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);
}
}

View 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.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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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 { }

View File

@ -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.");
}
}

View File

@ -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 { }

View 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;
}
}

View 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());
}
}

View 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;
}
}

View 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;
}

View 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
}

View 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;
}

View 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;
}
}

View 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;
}

View 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;
}

View 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
}
}

View File

@ -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();
}
}

View 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.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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View 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);
}
}
}

View 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
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View 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.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;
}

View File

@ -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> { }

View File

@ -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> { }

View File

@ -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> { }

View File

@ -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> { }

View 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;
}
}

View 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();
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -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>

View 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.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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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
View 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
View 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
View 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
View 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>

View 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 :(")));
}
}

View File

@ -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
View 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
View 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
View 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
View 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>

View 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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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