├── .github ├── dependabot.yml ├── funding.yml └── workflows │ ├── ci.yml │ ├── pr_tests.yml │ └── release.yml ├── .gitignore ├── HEADER ├── LICENSE ├── README.md ├── build.gradle ├── bukkit ├── build.gradle └── src │ └── main │ ├── java │ └── net │ │ └── william278 │ │ └── papiproxybridge │ │ ├── BukkitPAPIProxyBridge.java │ │ ├── messenger │ │ └── PluginMessageMessenger.java │ │ ├── papi │ │ └── Formatter.java │ │ └── user │ │ └── BukkitUser.java │ └── resources │ └── plugin.yml ├── bungee ├── build.gradle └── src │ └── main │ ├── java │ └── net │ │ └── william278 │ │ └── papiproxybridge │ │ ├── BungeePAPIProxyBridge.java │ │ ├── messenger │ │ └── PluginMessageMessenger.java │ │ └── user │ │ └── BungeeUser.java │ └── resources │ └── bungee.yml ├── common ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── william278 │ └── papiproxybridge │ ├── PAPIProxyBridge.java │ ├── api │ └── PlaceholderAPI.java │ ├── config │ └── Settings.java │ ├── messenger │ ├── Messenger.java │ └── redis │ │ ├── RedisMessenger.java │ │ ├── RedisPubSubListener.java │ │ └── StringByteArrayCodec.java │ └── user │ ├── OnlineUser.java │ └── Request.java ├── fabric ├── 1.20.1 │ ├── build.gradle │ ├── gradle.properties │ └── src │ │ └── main │ │ └── java │ │ └── net │ │ └── william278 │ │ └── papiproxybridge │ │ ├── messenger │ │ └── PluginMessageMessenger.java │ │ ├── payload │ │ ├── ComponentPayload.java │ │ ├── LiteralPayload.java │ │ └── TemplatePayload.java │ │ └── user │ │ └── FabricUser.java ├── 1.21.1 │ └── gradle.properties ├── 1.21.4 │ └── gradle.properties ├── 1.21.5 │ └── gradle.properties ├── build.gradle ├── gradle.properties ├── mainProject ├── root.gradle └── src │ └── main │ ├── java │ └── net │ │ └── william278 │ │ └── papiproxybridge │ │ ├── FabricPAPIProxyBridge.java │ │ ├── messenger │ │ └── PluginMessageMessenger.java │ │ ├── payload │ │ ├── ComponentPayload.java │ │ ├── LiteralPayload.java │ │ └── TemplatePayload.java │ │ └── user │ │ └── FabricUser.java │ └── resources │ └── fabric.mod.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── banner.png ├── proxy ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── william278 │ └── papiproxybridge │ ├── ProxyPAPIProxyBridge.java │ └── user │ └── ProxyUser.java ├── settings.gradle └── velocity ├── build.gradle └── src └── main ├── java └── net │ └── william278 │ └── papiproxybridge │ ├── VelocityPAPIProxyBridge.java │ ├── messenger │ └── PluginMessageMessenger.java │ └── user │ └── VelocityUser.java └── resources └── velocity-plugin.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file for GitHub 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "gradle" # See documentation for possible values 6 | directory: "/" # Location of package manifests 7 | schedule: 8 | interval: "daily" -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | # Funding metadata for GitHub 2 | 3 | github: WiIIiam278 4 | custom: https://buymeacoff.ee/william278 -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Tests 2 | 3 | on: 4 | push: 5 | branches: [ 'master' ] 6 | paths-ignore: 7 | - 'workflows/**' 8 | - 'README.md' 9 | 10 | permissions: 11 | contents: read 12 | checks: write 13 | 14 | jobs: 15 | build: 16 | name: 'Build' 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: 'Setup JDK 21 📦' 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: '21' 23 | distribution: 'temurin' 24 | - name: 'Setup Gradle 8.10 🏗️' 25 | uses: gradle/actions/setup-gradle@v4 26 | with: 27 | gradle-version: '8.10' 28 | - name: 'Checkout for CI 🛎️' 29 | uses: actions/checkout@v4 30 | env: 31 | SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 32 | SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} 33 | - name: 'Build 🛎️' 34 | run: | 35 | ./gradlew clean build publish 36 | - name: 'Publish Test Report 📊' 37 | uses: mikepenz/action-junit-report@v4 38 | if: success() || failure() # Continue on failure 39 | with: 40 | report_paths: '**/build/test-results/test/TEST-*.xml' 41 | - name: 'Fetch Version String 📝' 42 | run: | 43 | echo "::set-output name=VERSION_NAME::$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')" 44 | id: fetch-version 45 | - name: 'Set Version Variable 📝' 46 | run: | 47 | echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV 48 | - name: 'Publish to William278.net 🚀' 49 | uses: WiIIiam278/bones-publish-action@v1 50 | with: 51 | api-key: ${{ secrets.BONES_API_KEY }} 52 | project: 'papiproxybridge' 53 | channel: 'alpha' 54 | version: ${{ env.version_name }} 55 | changelog: ${{ github.event.head_commit.message }} 56 | distro-names: | 57 | spigot 58 | fabric-1.20.1 59 | fabric-1.21.1 60 | fabric-1.21.4 61 | fabric-1.21.5 62 | velocity 63 | bungeecord 64 | distro-groups: | 65 | spigot 66 | fabric 67 | fabric 68 | fabric 69 | fabric 70 | velocity 71 | bungeecord 72 | distro-descriptions: | 73 | Spigot 74 | Fabric 1.20.1 75 | Fabric 1.21.1 76 | Fabric 1.21.4 77 | Fabric 1.21.5 78 | Velocity 79 | BungeeCord 80 | files: | 81 | target/PAPIProxyBridge-Bukkit-${{ env.version_name }}.jar 82 | target/PAPIProxyBridge-Fabric-${{ env.version_name }}+mc.1.20.1.jar 83 | target/PAPIProxyBridge-Fabric-${{ env.version_name }}+mc.1.21.1.jar 84 | target/PAPIProxyBridge-Fabric-${{ env.version_name }}+mc.1.21.4.jar 85 | target/PAPIProxyBridge-Fabric-${{ env.version_name }}+mc.1.21.5.jar 86 | target/PAPIProxyBridge-Velocity-${{ env.version_name }}.jar 87 | target/PAPIProxyBridge-Bungee-${{ env.version_name }}.jar 88 | - name: 'Spigot: Publish to Modrinth' 89 | uses: WiIIiam278/mc-publish@hangar 90 | if: success() || failure() 91 | with: 92 | modrinth-id: bEIUEGTX 93 | modrinth-featured: false 94 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 95 | modrinth-dependencies: | 96 | placeholderapi | suggests | * 97 | files-primary: target/PAPIProxyBridge-Bukkit-${{ env.version_name }}.jar 98 | name: PAPIProxyBridge (Spigot) v${{ env.version_name }} 99 | version: ${{ env.version_name }} 100 | version-type: alpha 101 | changelog: ${{ github.event.head_commit.message }} 102 | loaders: | 103 | spigot 104 | paper 105 | folia 106 | game-versions: | 107 | 1.17.1 108 | 1.18 109 | 1.18.1 110 | 1.18.2 111 | 1.19 112 | 1.19.1 113 | 1.19.2 114 | 1.19.3 115 | 1.19.4 116 | 1.20 117 | 1.20.1 118 | 1.20.2 119 | 1.20.3 120 | 1.20.4 121 | 1.20.5 122 | 1.20.6 123 | 1.21 124 | 1.21.1 125 | 1.21.2 126 | 1.21.3 127 | 1.21.4 128 | 1.21.5 129 | java: 17 130 | - name: 'Fabric 1.20.1: Publish to Modrinth' 131 | uses: WiIIiam278/mc-publish@hangar 132 | if: success() || failure() 133 | with: 134 | modrinth-id: bEIUEGTX 135 | modrinth-featured: false 136 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 137 | modrinth-dependencies: | 138 | placeholder-api | suggests | * 139 | files-primary: target/PAPIProxyBridge-Fabric-${{ env.version_name }}+mc.1.20.1.jar 140 | name: PAPIProxyBridge (Fabric) v${{ env.version_name }} 141 | version: ${{ env.version_name }} 142 | version-type: alpha 143 | changelog: ${{ github.event.head_commit.message }} 144 | loaders: | 145 | fabric 146 | game-versions: | 147 | 1.20.1 148 | java: 17 149 | - name: 'Fabric 1.21.1: Publish to Modrinth' 150 | uses: WiIIiam278/mc-publish@hangar 151 | if: success() || failure() 152 | with: 153 | modrinth-id: bEIUEGTX 154 | modrinth-featured: false 155 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 156 | modrinth-dependencies: | 157 | placeholder-api | suggests | * 158 | files-primary: target/PAPIProxyBridge-Fabric-${{ env.version_name }}+mc.1.21.1.jar 159 | name: PAPIProxyBridge (Fabric) v${{ env.version_name }} 160 | version: ${{ env.version_name }} 161 | version-type: alpha 162 | changelog: ${{ github.event.head_commit.message }} 163 | loaders: | 164 | fabric 165 | game-versions: | 166 | 1.21.1 167 | java: 21 168 | - name: 'Fabric 1.21.4: Publish to Modrinth' 169 | uses: WiIIiam278/mc-publish@hangar 170 | if: success() || failure() 171 | with: 172 | modrinth-id: bEIUEGTX 173 | modrinth-featured: false 174 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 175 | modrinth-dependencies: | 176 | placeholder-api | suggests | * 177 | files-primary: target/PAPIProxyBridge-Fabric-${{ env.version_name }}+mc.1.21.4.jar 178 | name: PAPIProxyBridge (Fabric) v${{ env.version_name }} 179 | version: ${{ env.version_name }} 180 | version-type: alpha 181 | changelog: ${{ github.event.head_commit.message }} 182 | loaders: | 183 | fabric 184 | game-versions: | 185 | 1.21.4 186 | java: 21 187 | - name: 'Fabric 1.21.5: Publish to Modrinth' 188 | uses: WiIIiam278/mc-publish@hangar 189 | if: success() || failure() 190 | with: 191 | modrinth-id: bEIUEGTX 192 | modrinth-featured: false 193 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 194 | modrinth-dependencies: | 195 | placeholder-api | suggests | * 196 | files-primary: target/PAPIProxyBridge-Fabric-${{ env.version_name }}+mc.1.21.5.jar 197 | name: PAPIProxyBridge (Fabric) v${{ env.version_name }} 198 | version: ${{ env.version_name }} 199 | version-type: alpha 200 | changelog: ${{ github.event.head_commit.message }} 201 | loaders: | 202 | fabric 203 | game-versions: | 204 | 1.21.5 205 | java: 21 206 | - name: 'Bungee: Publish to Modrinth' 207 | uses: WiIIiam278/mc-publish@hangar 208 | if: success() || failure() 209 | with: 210 | modrinth-id: bEIUEGTX 211 | modrinth-featured: false 212 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 213 | files-primary: target/PAPIProxyBridge-Bungee-${{ env.version_name }}.jar 214 | name: PAPIProxyBridge (Bungee) v${{ env.version_name }} 215 | version: ${{ env.version_name }} 216 | version-type: alpha 217 | changelog: ${{ github.event.head_commit.message }} 218 | loaders: | 219 | bungeecord 220 | waterfall 221 | game-versions: | 222 | 1.17.1 223 | 1.18 224 | 1.18.1 225 | 1.18.2 226 | 1.19 227 | 1.19.1 228 | 1.19.2 229 | 1.19.3 230 | 1.19.4 231 | 1.20 232 | 1.20.1 233 | 1.20.2 234 | 1.20.3 235 | 1.20.4 236 | 1.20.5 237 | 1.20.6 238 | 1.21 239 | 1.21.1 240 | 1.21.2 241 | 1.21.3 242 | 1.21.4 243 | 1.21.5 244 | java: 21 245 | - name: 'Velocity: Publish to Modrinth & Hangar' 246 | uses: WiIIiam278/mc-publish@hangar 247 | if: success() || failure() 248 | with: 249 | modrinth-id: bEIUEGTX 250 | modrinth-featured: false 251 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 252 | modrinth-dependencies: | 253 | placeholderapi | suggests | * 254 | # hangar-id: William278/PAPIProxyBridge 255 | # hangar-token: ${{ secrets.HANGAR_API_KEY }} 256 | # hangar-version-type: Alpha 257 | files-primary: target/PAPIProxyBridge-Velocity-${{ env.version_name }}.jar 258 | name: PAPIProxyBridge (Velocity) v${{ env.version_name }} 259 | version: ${{ env.version_name }} 260 | version-type: alpha 261 | changelog: ${{ github.event.head_commit.message }} 262 | loaders: | 263 | velocity 264 | game-versions: | 265 | 1.17.1 266 | 1.18 267 | 1.18.1 268 | 1.18.2 269 | 1.19 270 | 1.19.1 271 | 1.19.2 272 | 1.19.3 273 | 1.19.4 274 | 1.20 275 | 1.20.1 276 | 1.20.2 277 | 1.20.3 278 | 1.20.4 279 | 1.20.5 280 | 1.20.6 281 | 1.21 282 | 1.21.1 283 | 1.21.2 284 | 1.21.3 285 | 1.21.4 286 | 1.21.5 287 | java: 17 -------------------------------------------------------------------------------- /.github/workflows/pr_tests.yml: -------------------------------------------------------------------------------- 1 | # Carry out tests on pull requests 2 | name: PR Tests 3 | 4 | on: 5 | pull_request: 6 | branches: [ 'master' ] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up JDK 21 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '21' 20 | distribution: 'temurin' 21 | - name: Test Pull Request 22 | uses: gradle/gradle-build-action@v2 23 | with: 24 | arguments: build test -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Builds, tests and publishes to maven when a release is published 2 | name: Release Tests 3 | 4 | on: 5 | release: 6 | types: [ published ] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up JDK 21 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '21' 20 | distribution: 'temurin' 21 | - name: Build with Gradle 22 | uses: gradle/gradle-build-action@v2 23 | with: 24 | arguments: build test publish 25 | env: 26 | RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 27 | RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} 28 | - name: 'Publish to William278.net 🚀' 29 | uses: WiIIiam278/bones-publish-action@v1 30 | with: 31 | api-key: ${{ secrets.BONES_API_KEY }} 32 | project: 'papiproxybridge' 33 | channel: 'release' 34 | version: ${{ github.event.release.tag_name }} 35 | changelog: ${{ github.event.release.body }} 36 | distro-names: | 37 | spigot 38 | fabric-1.20.1 39 | fabric-1.21.1 40 | fabric-1.21.4 41 | fabric-1.21.5 42 | velocity 43 | bungeecord 44 | distro-groups: | 45 | spigot 46 | fabric 47 | fabric 48 | fabric 49 | fabric 50 | velocity 51 | bungeecord 52 | distro-descriptions: | 53 | Spigot 54 | Fabric 1.20.1 55 | Fabric 1.21.1 56 | Fabric 1.21.4 57 | Fabric 1.21.5 58 | Velocity 59 | BungeeCord 60 | files: | 61 | target/PAPIProxyBridge-Bukkit-${{ github.event.release.tag_name }}.jar 62 | target/PAPIProxyBridge-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar 63 | target/PAPIProxyBridge-Fabric-${{ github.event.release.tag_name }}+mc.1.21.1.jar 64 | target/PAPIProxyBridge-Fabric-${{ github.event.release.tag_name }}+mc.1.21.4.jar 65 | target/PAPIProxyBridge-Fabric-${{ github.event.release.tag_name }}+mc.1.21.5.jar 66 | target/PAPIProxyBridge-Velocity-${{ github.event.release.tag_name }}.jar 67 | target/PAPIProxyBridge-Bungee-${{ github.event.release.tag_name }}.jar 68 | - name: 'Spigot: Publish to Modrinth' 69 | uses: WiIIiam278/mc-publish@hangar 70 | if: success() || failure() 71 | with: 72 | modrinth-id: bEIUEGTX 73 | modrinth-featured: false 74 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 75 | modrinth-dependencies: | 76 | placeholderapi | suggests | * 77 | files-primary: target/PAPIProxyBridge-Bukkit-${{ github.event.release.tag_name }}.jar 78 | name: PAPIProxyBridge (Spigot) v${{ github.event.release.tag_name }} 79 | version: ${{ github.event.release.tag_name }} 80 | version-type: release 81 | changelog: ${{ github.event.release.body }} 82 | loaders: | 83 | spigot 84 | paper 85 | folia 86 | game-versions: | 87 | 1.17.1 88 | 1.18 89 | 1.18.1 90 | 1.18.2 91 | 1.19 92 | 1.19.1 93 | 1.19.2 94 | 1.19.3 95 | 1.19.4 96 | 1.20 97 | 1.20.1 98 | 1.20.2 99 | 1.20.3 100 | 1.20.4 101 | 1.20.5 102 | 1.20.6 103 | 1.21 104 | 1.21.1 105 | 1.21.2 106 | 1.21.3 107 | 1.21.4 108 | java: 17 109 | - name: 'Fabric 1.20.1: Publish to Modrinth' 110 | uses: WiIIiam278/mc-publish@hangar 111 | if: success() || failure() 112 | with: 113 | modrinth-id: bEIUEGTX 114 | modrinth-featured: false 115 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 116 | modrinth-dependencies: | 117 | placeholder-api | suggests | * 118 | files-primary: target/PAPIProxyBridge-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar 119 | name: PAPIProxyBridge (Fabric) v${{ github.event.release.tag_name }} 120 | version: ${{ github.event.release.tag_name }} 121 | version-type: release 122 | changelog: ${{ github.event.release.body }} 123 | loaders: | 124 | fabric 125 | game-versions: | 126 | 1.20.1 127 | java: 17 128 | - name: 'Fabric 1.21.1: Publish to Modrinth' 129 | uses: WiIIiam278/mc-publish@hangar 130 | if: success() || failure() 131 | with: 132 | modrinth-id: bEIUEGTX 133 | modrinth-featured: false 134 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 135 | modrinth-dependencies: | 136 | placeholder-api | suggests | * 137 | files-primary: target/PAPIProxyBridge-Fabric-${{ github.event.release.tag_name }}+mc.1.21.1.jar 138 | name: PAPIProxyBridge (Fabric) v${{ github.event.release.tag_name }} 139 | version: ${{ github.event.release.tag_name }} 140 | version-type: release 141 | changelog: ${{ github.event.release.body }} 142 | loaders: | 143 | fabric 144 | game-versions: | 145 | 1.21.1 146 | java: 21 147 | - name: 'Fabric 1.21.4: Publish to Modrinth' 148 | uses: WiIIiam278/mc-publish@hangar 149 | if: success() || failure() 150 | with: 151 | modrinth-id: bEIUEGTX 152 | modrinth-featured: false 153 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 154 | modrinth-dependencies: | 155 | placeholder-api | suggests | * 156 | files-primary: target/PAPIProxyBridge-Fabric-${{ github.event.release.tag_name }}+mc.1.21.4.jar 157 | name: PAPIProxyBridge (Fabric) v${{ github.event.release.tag_name }} 158 | version: ${{ github.event.release.tag_name }} 159 | version-type: release 160 | changelog: ${{ github.event.release.body }} 161 | loaders: | 162 | fabric 163 | game-versions: | 164 | 1.21.4 165 | java: 21 166 | - name: 'Fabric 1.21.5: Publish to Modrinth' 167 | uses: WiIIiam278/mc-publish@hangar 168 | if: success() || failure() 169 | with: 170 | modrinth-id: bEIUEGTX 171 | modrinth-featured: false 172 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 173 | modrinth-dependencies: | 174 | placeholder-api | suggests | * 175 | files-primary: target/PAPIProxyBridge-Fabric-${{ github.event.release.tag_name }}+mc.1.21.5.jar 176 | name: PAPIProxyBridge (Fabric) v${{ github.event.release.tag_name }} 177 | version: ${{ github.event.release.tag_name }} 178 | version-type: release 179 | changelog: ${{ github.event.release.body }} 180 | loaders: | 181 | fabric 182 | game-versions: | 183 | 1.21.5 184 | java: 21 185 | - name: 'Bungee: Publish to Modrinth' 186 | uses: WiIIiam278/mc-publish@hangar 187 | if: success() || failure() 188 | with: 189 | modrinth-id: bEIUEGTX 190 | modrinth-featured: false 191 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 192 | files-primary: target/PAPIProxyBridge-Bungee-${{ github.event.release.tag_name }}.jar 193 | name: PAPIProxyBridge (Bungee) v${{ github.event.release.tag_name }} 194 | version: ${{ github.event.release.tag_name }} 195 | version-type: release 196 | changelog: ${{ github.event.release.body }} 197 | loaders: | 198 | bungeecord 199 | waterfall 200 | game-versions: | 201 | 1.17.1 202 | 1.18 203 | 1.18.1 204 | 1.18.2 205 | 1.19 206 | 1.19.1 207 | 1.19.2 208 | 1.19.3 209 | 1.19.4 210 | 1.20 211 | 1.20.1 212 | 1.20.2 213 | 1.20.3 214 | 1.20.4 215 | 1.20.5 216 | 1.20.6 217 | 1.21 218 | 1.21.1 219 | 1.21.2 220 | 1.21.3 221 | 1.21.4 222 | 1.21.5 223 | java: 21 224 | - name: 'Velocity: Publish to Modrinth & Hangar' 225 | uses: WiIIiam278/mc-publish@hangar 226 | if: success() || failure() 227 | with: 228 | modrinth-id: bEIUEGTX 229 | modrinth-featured: false 230 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 231 | modrinth-dependencies: | 232 | placeholderapi | suggests | * 233 | # hangar-id: William278/PAPIProxyBridge 234 | # hangar-token: ${{ secrets.HANGAR_API_KEY }} 235 | # hangar-version-type: Release 236 | files-primary: target/PAPIProxyBridge-Velocity-${{ github.event.release.tag_name }}.jar 237 | name: PAPIProxyBridge (Velocity) v${{ github.event.release.tag_name }} 238 | version: ${{ github.event.release.tag_name }} 239 | version-type: release 240 | changelog: ${{ github.event.release.body }} 241 | loaders: | 242 | velocity 243 | game-versions: | 244 | 1.17.1 245 | 1.18 246 | 1.18.1 247 | 1.18.2 248 | 1.19 249 | 1.19.1 250 | 1.19.2 251 | 1.19.3 252 | 1.19.4 253 | 1.20 254 | 1.20.1 255 | 1.20.2 256 | 1.20.3 257 | 1.20.4 258 | 1.20.5 259 | 1.20.6 260 | 1.21 261 | 1.21.1 262 | 1.21.2 263 | 1.21.3 264 | 1.21.4 265 | 1.21.5 266 | java: 17 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Build/run paths ### 2 | build/ 3 | run/ 4 | .run/ 5 | target/ 6 | 7 | ### Gradle ### 8 | .gradle 9 | !gradle/wrapper/gradle-wrapper.jar 10 | 11 | ### IntelliJ IDEA ### 12 | .idea/ 13 | out/ 14 | *.iws 15 | *.iml 16 | *.ipr 17 | !**/src/main/**/out/ 18 | !**/src/test/**/out/ 19 | 20 | ### Eclipse ### 21 | .apt_generated 22 | .classpath 23 | .factorypath 24 | .project 25 | .settings 26 | .springBeans 27 | .sts4-cache 28 | bin/ 29 | !**/src/main/**/bin/ 30 | !**/src/test/**/bin/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store 44 | -------------------------------------------------------------------------------- /HEADER: -------------------------------------------------------------------------------- 1 | This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 2 | 3 | Copyright (c) William278 4 | Copyright (c) contributors 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | PAPIProxyBridge 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

14 |
15 | 16 | **PAPIProxyBridge** is a library bridge plugin you install on both your backend and proxy servers that allows proxy plugins to format text with PlaceholderAPI placeholders. 17 | 18 | ## For server owners 19 | This is a library plugin intended for use with plugins that implement its API. There is nothing to configure. 20 | 21 | Install the latest version of the plugin alongside the [PlaceholderAPI plugin](https://www.spigotmc.org/resources/placeholderapi.6245/) on your Spigot (1.16.5+) or the [PlaceholderAPI mod](https://placeholders.pb4.eu/) on your Fabric (1.20) server, then install the plugin on your BungeeCord or Velocity proxy server. 22 | 23 | Note this plugin is not a replacement for PlaceholderAPI. You still need to install PlaceholderAPI on your Spigot/Fabric server. 24 | 25 | ## For developers 26 | PAPIProxyBridge exposes a cross-platform API to let you format text with PlaceholderAPI placeholders. 27 | 28 |
29 | Adding the library to your project 30 | 31 | PAPIProxyBridge is available on `repo.william278.net` ([view javadocs here](https://repo.william278.net/javadoc/releases/net/william278/papiproxybridge/latest)). First, add the maven repository to your `build.gradle`: 32 | ```groovy 33 | repositories { 34 | maven { url 'https://repo.william278.net/releases/' } 35 | } 36 | ``` 37 | 38 | Then add the dependency: 39 | ```groovy 40 | dependencies { 41 | implementation 'net.william278:papiproxybridge:1.7.2 42 | } 43 | ``` 44 | 45 |
46 | 47 |
48 | Example usage 49 | 50 | The `PlaceholderAPI` class exposes the API for formatting placeholders. At the moment, only singleton non-bracketed placeholders are supported (more in the future). 51 | 52 | Get an instance of the class with PlaceholderAPI.getInstance(), then use the `#formatPlaceholders` method to format a string with placeholders on a player (specified with UUID for cross-platform simplicity). 53 | 54 | The method returns a [CompletableFuture](https://www.baeldung.com/java-completablefuture) (since we don't want to lock threads while the proxy networks with players on the backend) that you can use to accept the formatted string. 55 | 56 | ```java 57 | // Format a string with placeholders 58 | final PlaceholderAPI api = PlaceholderAPI.createInstance(); 59 | final UUID player = player.getUniqueId(); 60 | api.formatPlaceholders("Hello %player_name%!", player).thenAccept(formatted -> { 61 | player.sendMessage(formatted); 62 | }); 63 | ``` 64 | 65 | Never invoke `#join()` on calls to `#formatPlaceholders`; this is unsafe. 66 | 67 | PAPIProxyBridge caches resolved requests for 30000 milliseconds (30 seconds), to avoid causing excessive traffic over your server's network channels. You can adjust how long to cache requests for using the `PlaceholderAPI#setCacheExpiry(long)` method. 68 | 69 | There also exists `#formatComponentPlaceholders`. This method allows you to supply a string containing placeholders and receive an adventure component containing the formatted text, which may contain formatting and chat events. 70 |
71 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.filters.ReplaceTokens 2 | 3 | plugins { 4 | id 'com.gradleup.shadow' version '8.3.6' 5 | id 'org.cadixdev.licenser' version '0.6.1' apply false 6 | id 'dev.architectury.loom' version '1.9-SNAPSHOT' apply false 7 | id 'gg.essential.multi-version.root' apply false 8 | id 'org.ajoberstar.grgit' version '5.3.0' 9 | id 'maven-publish' 10 | id 'java' 11 | } 12 | 13 | group 'net.william278' 14 | version "$ext.plugin_version${versionMetadata()}" 15 | description "$ext.plugin_description" 16 | defaultTasks 'licenseFormat', 'build' 17 | 18 | ext { 19 | set 'version', version.toString() 20 | set 'description', description.toString() 21 | } 22 | 23 | publishing { 24 | repositories { 25 | if (System.getenv("RELEASES_MAVEN_USERNAME") != null) { 26 | maven { 27 | name = "william278-releases" 28 | url = "https://repo.william278.net/releases" 29 | credentials { 30 | username = System.getenv("RELEASES_MAVEN_USERNAME") 31 | password = System.getenv("RELEASES_MAVEN_PASSWORD") 32 | } 33 | authentication { 34 | basic(BasicAuthentication) 35 | } 36 | } 37 | } 38 | if (System.getenv("SNAPSHOTS_MAVEN_USERNAME") != null) { 39 | maven { 40 | name = "william278-snapshots" 41 | url = "https://repo.william278.net/snapshots" 42 | credentials { 43 | username = System.getenv("SNAPSHOTS_MAVEN_USERNAME") 44 | password = System.getenv("SNAPSHOTS_MAVEN_PASSWORD") 45 | } 46 | authentication { 47 | basic(BasicAuthentication) 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | allprojects { 55 | if (project.name == 'fabric') { 56 | return 57 | } 58 | 59 | apply plugin: 'com.gradleup.shadow' 60 | apply plugin: 'org.cadixdev.licenser' 61 | apply plugin: 'java' 62 | 63 | compileJava.options.encoding = 'UTF-8' 64 | compileJava.options.release.set 17 65 | javadoc.options.encoding = 'UTF-8' 66 | javadoc.options.addStringOption('Xdoclint:none', '-quiet') 67 | 68 | repositories { 69 | mavenLocal() 70 | mavenCentral() 71 | maven { url 'https://maven.fabricmc.net/' } 72 | // Fabric maven is above paper maven to get FabricAPi from it over PaperMC 73 | maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } 74 | maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } 75 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 76 | maven { url 'https://jitpack.io' } 77 | } 78 | 79 | dependencies { 80 | compileOnly 'org.projectlombok:lombok:1.18.38' 81 | annotationProcessor 'org.projectlombok:lombok:1.18.38' 82 | 83 | testCompileOnly 'org.projectlombok:lombok:1.18.38' 84 | testAnnotationProcessor 'org.projectlombok:lombok:1.18.38' 85 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.12.1' 86 | testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.12.1' 87 | } 88 | 89 | test { 90 | useJUnitPlatform() 91 | } 92 | 93 | license { 94 | header = rootProject.file('HEADER') 95 | include '**/*.java' 96 | newLine = true 97 | } 98 | 99 | processResources { 100 | filesMatching(['**/*.json', '**/*.yml']) { 101 | filter ReplaceTokens as Class, beginToken: '${', endToken: '}', 102 | tokens: rootProject.ext.properties 103 | } 104 | } 105 | 106 | jar { 107 | manifest { 108 | attributes 'Implementation-Version': version 109 | } 110 | } 111 | } 112 | 113 | subprojects { 114 | // Ignore root fabric project (no jars) 115 | if (project.name == 'fabric') { 116 | return 117 | } 118 | 119 | version rootProject.version 120 | // archivesBaseName = "${rootProject.name}-${project.name.capitalize()}" 121 | def name = "$rootProject.name" 122 | if (rootProject != project.parent) { 123 | name += "-${project.parent.name.capitalize()}" 124 | } else { 125 | name += "-${project.name.capitalize()}" 126 | } 127 | archivesBaseName = name 128 | 129 | if (project.parent?.name?.equals('fabric')) { 130 | apply plugin: 'dev.architectury.loom' 131 | compileJava.options.release.set (project.name == '1.20.1' ? 17 : 21) // 1.20.1 requires Java 17 132 | version += "+mc.${project.name}" 133 | } 134 | 135 | jar { 136 | from '../LICENSE' 137 | } 138 | 139 | shadowJar { 140 | destinationDirectory.set(file("$rootDir/target")) 141 | archiveClassifier.set('') 142 | } 143 | 144 | if (['common'].contains(project.name)) { 145 | java { 146 | withSourcesJar() 147 | withJavadocJar() 148 | } 149 | 150 | sourcesJar { 151 | destinationDirectory.set(file("$rootDir/target")) 152 | } 153 | javadocJar { 154 | destinationDirectory.set(file("$rootDir/target")) 155 | } 156 | jar.dependsOn(sourcesJar, javadocJar) 157 | 158 | publishing { 159 | publications { 160 | mavenJava(MavenPublication) { 161 | groupId = 'net.william278' 162 | artifactId = 'papiproxybridge' 163 | version = "$rootProject.version" 164 | artifact jar 165 | artifact javadocJar 166 | artifact sourcesJar 167 | } 168 | } 169 | } 170 | } 171 | 172 | if (['bukkit', 'bungee', 'velocity'].contains(project.name) || project.parent?.name?.equals('fabric')) { 173 | shadowJar { 174 | destinationDirectory.set(file("$rootDir/target")) 175 | archiveClassifier.set('') 176 | } 177 | } 178 | 179 | jar.dependsOn(shadowJar) 180 | clean.delete "$rootDir/target" 181 | } 182 | 183 | logger.lifecycle("Building PAPIProxyBridge ${version} by William278") 184 | 185 | @SuppressWarnings('GrMethodMayBeStatic') 186 | def versionMetadata() { 187 | // Get if there is a tag for this commit 188 | def tag = grgit.tag.list().find { it.commit.id == grgit.head().id } 189 | if (tag != null) { 190 | return '' 191 | } 192 | 193 | // Otherwise, get the last commit hash and if it's a clean head 194 | if (grgit == null) { 195 | return '-' + System.getenv("GITHUB_RUN_NUMBER") ? 'build.' + System.getenv("GITHUB_RUN_NUMBER") : 'unknown' 196 | } 197 | return '-' + grgit.head().abbreviatedId + (grgit.status().clean ? '' : '-indev') 198 | } -------------------------------------------------------------------------------- /bukkit/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation project(path: ':common') 3 | 4 | implementation 'org.bstats:bstats-bukkit:3.1.0' 5 | implementation 'net.kyori:adventure-platform-bukkit:4.3.4' 6 | implementation 'io.github.projectunified:minelib-scheduler-entity:1.2.5' 7 | 8 | compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT' 9 | compileOnly 'org.jetbrains:annotations:26.0.2' 10 | compileOnly 'me.clip:placeholderapi:2.11.6' 11 | compileOnly 'io.lettuce:lettuce-core:6.5.5.RELEASE' 12 | compileOnly 'de.exlll:configlib-yaml:4.5.0' 13 | } 14 | 15 | shadowJar { 16 | relocate 'net.jodah', 'net.william278.papiproxybridge.libraries' 17 | relocate 'org.bstats', 'net.william278.papiproxybridge.libraries.bstats' 18 | relocate 'io.github.projectunified.minelib', 'net.william278.papiproxybridge.libraries.minelib' 19 | 20 | minimize() 21 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/net/william278/papiproxybridge/BukkitPAPIProxyBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge; 21 | 22 | import com.google.common.collect.Maps; 23 | import lombok.Getter; 24 | import lombok.Setter; 25 | import net.william278.papiproxybridge.api.PlaceholderAPI; 26 | import net.william278.papiproxybridge.config.Settings; 27 | import net.william278.papiproxybridge.messenger.Messenger; 28 | import net.william278.papiproxybridge.messenger.PluginMessageMessenger; 29 | import net.william278.papiproxybridge.messenger.redis.RedisMessenger; 30 | import net.william278.papiproxybridge.papi.Formatter; 31 | import net.william278.papiproxybridge.user.BukkitUser; 32 | import net.william278.papiproxybridge.user.OnlineUser; 33 | import org.bstats.bukkit.Metrics; 34 | import org.bstats.charts.SimplePie; 35 | import org.bukkit.event.EventHandler; 36 | import org.bukkit.event.Listener; 37 | import org.bukkit.event.player.PlayerJoinEvent; 38 | import org.bukkit.event.player.PlayerQuitEvent; 39 | import org.bukkit.plugin.java.JavaPlugin; 40 | import org.jetbrains.annotations.NotNull; 41 | 42 | import java.util.*; 43 | import java.util.concurrent.CompletableFuture; 44 | import java.util.concurrent.ExecutorService; 45 | import java.util.concurrent.Executors; 46 | import java.util.logging.Level; 47 | 48 | @Getter 49 | public class BukkitPAPIProxyBridge extends JavaPlugin implements PAPIProxyBridge, Listener { 50 | 51 | private Formatter formatter; 52 | private Map users; 53 | @Setter 54 | private Settings settings; 55 | private Messenger messenger; 56 | private ExecutorService executorService; 57 | 58 | @Override 59 | public void onLoad() { 60 | users = Maps.newConcurrentMap(); 61 | executorService = Executors.newFixedThreadPool(2); 62 | // Initialize the formatter 63 | formatter = new Formatter(); 64 | } 65 | 66 | @Override 67 | public void onEnable() { 68 | loadConfig(); 69 | loadMessenger(); 70 | messenger.onEnable(); 71 | // Register the plugin message channel 72 | 73 | // Register the plugin with the API 74 | PlaceholderAPI.register(this); 75 | 76 | // Register events 77 | getServer().getPluginManager().registerEvents(this, this); 78 | 79 | // Load online players 80 | loadOnlinePlayers(); 81 | 82 | // Metrics 83 | setupMetrics(); 84 | 85 | getLogger().info(getLoadMessage()); 86 | } 87 | 88 | @Override 89 | public void onDisable() { 90 | messenger.onDisable(); 91 | } 92 | 93 | private void setupMetrics() { 94 | final Metrics metrics = new Metrics(this, 17880); 95 | metrics.addCustomChart(new SimplePie("messengerType", () -> getSettings().getMessenger().name())); 96 | } 97 | 98 | private void loadOnlinePlayers() { 99 | users.clear(); 100 | getServer().getOnlinePlayers().forEach(player -> { 101 | final BukkitUser user = BukkitUser.adapt(player); 102 | users.put(player.getUniqueId(), user); 103 | }); 104 | } 105 | 106 | @Override 107 | public String getServerType() { 108 | return getServer().getName(); 109 | } 110 | 111 | @Override 112 | @NotNull 113 | public Collection getOnlineUsers() { 114 | return users.values(); 115 | } 116 | 117 | @Override 118 | public Optional findPlayer(@NotNull UUID uuid) { 119 | return Optional.ofNullable(users.get(uuid)); 120 | } 121 | 122 | @Override 123 | public CompletableFuture createRequest(@NotNull String text, @NotNull OnlineUser requester, @NotNull UUID formatFor, boolean wantsJson, long requestTimeout) { 124 | return formatPlaceholders(formatFor, (BukkitUser) requester, text); 125 | } 126 | 127 | @Override 128 | public CompletableFuture> getServers(long requestTimeout) { 129 | throw new UnsupportedOperationException("Cannot fetch the list of servers from a backend Bukkit server."); 130 | } 131 | 132 | @Override 133 | public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... exceptions) { 134 | if (exceptions.length > 0) { 135 | getLogger().log(level, message, exceptions[0]); 136 | } else { 137 | getLogger().log(level, message); 138 | } 139 | } 140 | 141 | @NotNull 142 | public final CompletableFuture formatPlaceholders(@NotNull UUID formatFor, @NotNull BukkitUser requester, @NotNull String text) { 143 | return CompletableFuture.supplyAsync(() -> formatter.formatPlaceholders(formatFor, requester.player(), text), executorService); 144 | } 145 | 146 | @EventHandler 147 | public void onJoin(PlayerJoinEvent event) { 148 | final BukkitUser user = BukkitUser.adapt(event.getPlayer()); 149 | users.put(user.getUniqueId(), user); 150 | } 151 | 152 | @EventHandler 153 | public void onQuit(PlayerQuitEvent event) { 154 | users.remove(event.getPlayer().getUniqueId()); 155 | } 156 | 157 | @Override 158 | public void loadMessenger() { 159 | switch (settings.getMessenger()) { 160 | case REDIS -> messenger = new RedisMessenger(this, settings.getRedis(), true); 161 | case PLUGIN_MESSAGE -> messenger = new PluginMessageMessenger(this); 162 | } 163 | log(Level.INFO, "Loaded messenger " + settings.getMessenger().name()); 164 | } 165 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/net/william278/papiproxybridge/messenger/PluginMessageMessenger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger; 21 | 22 | import lombok.RequiredArgsConstructor; 23 | import net.william278.papiproxybridge.BukkitPAPIProxyBridge; 24 | import net.william278.papiproxybridge.PAPIProxyBridge; 25 | import org.bukkit.Bukkit; 26 | import org.bukkit.entity.Player; 27 | import org.bukkit.plugin.messaging.PluginMessageListener; 28 | import org.jetbrains.annotations.NotNull; 29 | 30 | import java.util.UUID; 31 | 32 | @RequiredArgsConstructor 33 | public class PluginMessageMessenger extends Messenger implements PluginMessageListener { 34 | 35 | private final BukkitPAPIProxyBridge plugin; 36 | 37 | @Override 38 | public void onEnable() { 39 | plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, PAPIProxyBridge.getChannel(false)); 40 | plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, PAPIProxyBridge.getComponentChannel(false)); 41 | plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, PAPIProxyBridge.getChannel(true), this); 42 | plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, PAPIProxyBridge.getComponentChannel(true), this); 43 | } 44 | 45 | @Override 46 | public void onDisable() { 47 | plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin); 48 | plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin); 49 | } 50 | 51 | @Override 52 | public void sendMessage(@NotNull UUID uuid, @NotNull String channel, byte @NotNull [] message) { 53 | final Player player = Bukkit.getPlayer(uuid); 54 | if (player == null) { 55 | return; 56 | } 57 | 58 | player.sendPluginMessage(plugin, channel, message); 59 | } 60 | 61 | @Override 62 | public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message) { 63 | plugin.getExecutorService().submit(() -> plugin.handleMessage(plugin, channel, message, true)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /bukkit/src/main/java/net/william278/papiproxybridge/papi/Formatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.papi; 21 | 22 | import me.clip.placeholderapi.PlaceholderAPI; 23 | import net.william278.papiproxybridge.PAPIProxyBridge; 24 | import org.bukkit.Bukkit; 25 | import org.bukkit.entity.Player; 26 | import org.jetbrains.annotations.NotNull; 27 | 28 | import java.util.UUID; 29 | 30 | public class Formatter { 31 | 32 | @NotNull 33 | public final String formatPlaceholders(@NotNull UUID formatFor, @NotNull Player requester, @NotNull String text) { 34 | text = text.replaceAll(PAPIProxyBridge.HANDSHAKE_PLACEHOLDER, PAPIProxyBridge.HANDSHAKE_RESPONSE); 35 | return PlaceholderAPI.setPlaceholders( 36 | requester.getUniqueId().equals(formatFor) ? requester : Bukkit.getOfflinePlayer(formatFor), 37 | text 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bukkit/src/main/java/net/william278/papiproxybridge/user/BukkitUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.user; 21 | 22 | import me.clip.placeholderapi.libs.kyori.adventure.text.Component; 23 | import me.clip.placeholderapi.libs.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 24 | import net.william278.papiproxybridge.BukkitPAPIProxyBridge; 25 | import net.william278.papiproxybridge.PAPIProxyBridge; 26 | import org.bukkit.entity.Player; 27 | import org.jetbrains.annotations.NotNull; 28 | 29 | import java.util.UUID; 30 | 31 | public record BukkitUser(Player player) implements OnlineUser { 32 | 33 | @NotNull 34 | public static BukkitUser adapt(@NotNull Player player) { 35 | return new BukkitUser(player); 36 | } 37 | 38 | @Override 39 | @NotNull 40 | public String getUsername() { 41 | return player.getName(); 42 | } 43 | 44 | @Override 45 | @NotNull 46 | public UUID getUniqueId() { 47 | return player.getUniqueId(); 48 | } 49 | 50 | @Override 51 | public void handleMessage(@NotNull PAPIProxyBridge plugin, @NotNull Request message, boolean wantsJson) { 52 | ((BukkitPAPIProxyBridge) plugin).formatPlaceholders(message.getFormatFor(), this, message.getMessage()).thenAccept(formatted -> { 53 | message.setMessage(wantsJson ? GsonComponentSerializer.gson().serialize(Component.text(formatted)) : formatted); 54 | this.sendMessage(plugin, message, wantsJson, false); 55 | }); 56 | } 57 | 58 | @Override 59 | @NotNull 60 | public Player player() { 61 | return player; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /bukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: 'PAPIProxyBridge' 2 | version: '${version}' 3 | description: '${description}' 4 | author: 'William278' 5 | website: 'https://william278.net/' 6 | main: 'net.william278.papiproxybridge.BukkitPAPIProxyBridge' 7 | api-version: 1.16 8 | folia-supported: true 9 | depend: [ PlaceholderAPI ] 10 | libraries: 11 | - 'io.lettuce:lettuce-core:6.5.2.RELEASE' 12 | - 'de.exlll:configlib-yaml:4.5.0' -------------------------------------------------------------------------------- /bungee/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation project(path: ':proxy') 3 | implementation project(path: ':common') 4 | 5 | implementation 'org.bstats:bstats-bungeecord:3.1.0' 6 | implementation 'net.kyori:adventure-platform-bungeecord:4.3.4' 7 | implementation 'io.lettuce:lettuce-core:6.5.5.RELEASE' 8 | implementation 'de.exlll:configlib-yaml:4.5.0' 9 | 10 | compileOnly 'net.md-5:bungeecord-api:1.16-R0.5-SNAPSHOT' 11 | compileOnly 'org.jetbrains:annotations:26.0.2' 12 | } 13 | 14 | shadowJar { 15 | relocate 'net.jodah', 'net.william278.papiproxybridge.libraries' 16 | relocate 'org.bstats', 'net.william278.papiproxybridge.libraries.bstats' 17 | relocate 'org.snakeyaml', 'net.william278.papiproxybridge.libraries.snakeyaml' 18 | relocate 'io.lettuce', 'net.william278.papiproxybridge.libraries.lettuce' 19 | relocate 'de.exlll', 'net.william278.papiproxybridge.libraries.configlib' 20 | relocate 'io.netty', 'net.william278.papiproxybridge.libraries.netty' 21 | relocate 'org.reactivestreams', 'net.william278.papiproxybridge.libraries.reactivestreams' 22 | relocate 'reactor', 'net.william278.papiproxybridge.libraries.reactor' 23 | 24 | minimize() 25 | } -------------------------------------------------------------------------------- /bungee/src/main/java/net/william278/papiproxybridge/BungeePAPIProxyBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge; 21 | 22 | import com.google.common.collect.Maps; 23 | import lombok.Getter; 24 | import lombok.Setter; 25 | import net.md_5.bungee.api.event.PluginMessageEvent; 26 | import net.md_5.bungee.api.event.PostLoginEvent; 27 | import net.md_5.bungee.api.plugin.Listener; 28 | import net.md_5.bungee.api.plugin.Plugin; 29 | import net.md_5.bungee.event.EventHandler; 30 | import net.william278.papiproxybridge.api.PlaceholderAPI; 31 | import net.william278.papiproxybridge.config.Settings; 32 | import net.william278.papiproxybridge.messenger.Messenger; 33 | import net.william278.papiproxybridge.messenger.PluginMessageMessenger; 34 | import net.william278.papiproxybridge.messenger.redis.RedisMessenger; 35 | import net.william278.papiproxybridge.user.BungeeUser; 36 | import org.bstats.bungeecord.Metrics; 37 | import org.bstats.charts.SimplePie; 38 | import org.jetbrains.annotations.NotNull; 39 | 40 | import java.util.*; 41 | import java.util.concurrent.CompletableFuture; 42 | import java.util.concurrent.ConcurrentMap; 43 | import java.util.logging.Level; 44 | 45 | @SuppressWarnings("unused") 46 | @Getter 47 | public class BungeePAPIProxyBridge extends Plugin implements ProxyPAPIProxyBridge, Listener { 48 | 49 | private ConcurrentMap> requests; 50 | private Map users; 51 | @Setter 52 | private Settings settings; 53 | private Messenger messenger; 54 | 55 | @Override 56 | public void onEnable() { 57 | requests = Maps.newConcurrentMap(); 58 | users = Maps.newConcurrentMap(); 59 | loadConfig(); 60 | loadMessenger(); 61 | messenger.onEnable(); 62 | 63 | // Register the plugin message listener 64 | getProxy().getPluginManager().registerListener(this, this); 65 | 66 | // Register the plugin with the API 67 | PlaceholderAPI.register(this); 68 | 69 | // Metrics 70 | setupMetrics(); 71 | 72 | getLogger().info(getLoadMessage()); 73 | } 74 | 75 | @Override 76 | public void onDisable() { 77 | messenger.onDisable(); 78 | // Unregister the plugin message listener 79 | getProxy().getPluginManager().unregisterListener(this); 80 | } 81 | 82 | @EventHandler 83 | public void onPluginMessageReceived(PluginMessageEvent event) { 84 | this.handleMessage(this, event.getTag(), event.getData(), false); 85 | } 86 | 87 | @EventHandler 88 | public void onJoin(PostLoginEvent event) { 89 | final BungeeUser user = BungeeUser.adapt(event.getPlayer()); 90 | users.put(user.getUniqueId(), user); 91 | } 92 | 93 | @EventHandler 94 | public void onQuit(PostLoginEvent event) { 95 | final BungeeUser user = BungeeUser.adapt(event.getPlayer()); 96 | users.remove(user.getUniqueId()); 97 | PlaceholderAPI.clearCache(event.getPlayer().getUniqueId()); 98 | } 99 | 100 | private void setupMetrics() { 101 | final Metrics metrics = new Metrics(this, 17879); 102 | metrics.addCustomChart(new SimplePie("messengerType", () -> getSettings().getMessenger().name())); 103 | } 104 | 105 | @Override 106 | public String getServerType() { 107 | return "BungeeCord"; 108 | } 109 | 110 | @Override 111 | @NotNull 112 | public Collection getOnlineUsers() { 113 | return users.values(); 114 | } 115 | 116 | @Override 117 | public Optional findPlayer(@NotNull UUID uuid) { 118 | return Optional.ofNullable(users.get(uuid)); 119 | } 120 | 121 | @Override 122 | public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... exceptions) { 123 | if (exceptions.length > 0) { 124 | getLogger().log(level, message, exceptions[0]); 125 | } else { 126 | getLogger().log(level, message); 127 | } 128 | } 129 | 130 | @Override 131 | public void loadMessenger() { 132 | switch (settings.getMessenger()) { 133 | case REDIS -> messenger = new RedisMessenger(this, settings.getRedis(), false); 134 | case PLUGIN_MESSAGE -> messenger = new PluginMessageMessenger(this); 135 | } 136 | 137 | log(Level.INFO, "Loaded messenger " + settings.getMessenger().name()); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /bungee/src/main/java/net/william278/papiproxybridge/messenger/PluginMessageMessenger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger; 21 | 22 | import lombok.RequiredArgsConstructor; 23 | import net.md_5.bungee.api.connection.ProxiedPlayer; 24 | import net.md_5.bungee.api.plugin.Listener; 25 | import net.md_5.bungee.event.EventHandler; 26 | import net.william278.papiproxybridge.BungeePAPIProxyBridge; 27 | import net.william278.papiproxybridge.PAPIProxyBridge; 28 | import net.william278.papiproxybridge.user.BungeeUser; 29 | import org.jetbrains.annotations.NotNull; 30 | 31 | import java.util.Optional; 32 | import java.util.UUID; 33 | 34 | @RequiredArgsConstructor 35 | public class PluginMessageMessenger extends Messenger implements Listener { 36 | 37 | private final BungeePAPIProxyBridge plugin; 38 | 39 | @Override 40 | public void onEnable() { 41 | // Register the plugin message channel 42 | plugin.getProxy().registerChannel(PAPIProxyBridge.getChannel(true)); 43 | plugin.getProxy().registerChannel(PAPIProxyBridge.getComponentChannel(true)); 44 | plugin.getProxy().registerChannel(PAPIProxyBridge.getChannel(false)); 45 | plugin.getProxy().registerChannel(PAPIProxyBridge.getComponentChannel(false)); 46 | 47 | plugin.getProxy().getPluginManager().registerListener(plugin, this); 48 | } 49 | 50 | @Override 51 | public void sendMessage(@NotNull UUID uuid, @NotNull String channel, byte @NotNull [] message) { 52 | final Optional optionalBungeeUser = plugin.findPlayer(uuid); 53 | if (optionalBungeeUser.isEmpty()) { 54 | return; 55 | } 56 | 57 | final BungeeUser user = optionalBungeeUser.get(); 58 | final ProxiedPlayer player = user.player(); 59 | player.getServer().getInfo().sendData(channel, message); 60 | } 61 | 62 | @EventHandler 63 | public void onPluginMessageReceived(net.md_5.bungee.api.event.PluginMessageEvent event) { 64 | plugin.handleMessage(plugin, event.getTag(), event.getData(), false); 65 | } 66 | 67 | @Override 68 | public void onDisable() { 69 | plugin.getProxy().unregisterChannel(PAPIProxyBridge.getChannel(true)); 70 | plugin.getProxy().unregisterChannel(PAPIProxyBridge.getComponentChannel(true)); 71 | plugin.getProxy().unregisterChannel(PAPIProxyBridge.getChannel(false)); 72 | plugin.getProxy().unregisterChannel(PAPIProxyBridge.getComponentChannel(false)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /bungee/src/main/java/net/william278/papiproxybridge/user/BungeeUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.user; 21 | 22 | import net.md_5.bungee.api.connection.ProxiedPlayer; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.util.UUID; 26 | 27 | public record BungeeUser(ProxiedPlayer player) implements ProxyUser { 28 | 29 | @NotNull 30 | public static BungeeUser adapt(@NotNull ProxiedPlayer player) { 31 | return new BungeeUser(player); 32 | } 33 | 34 | @Override 35 | @NotNull 36 | public String getUsername() { 37 | return player.getName(); 38 | } 39 | 40 | @Override 41 | @NotNull 42 | public UUID getUniqueId() { 43 | return player.getUniqueId(); 44 | } 45 | 46 | @Override 47 | @NotNull 48 | public String getServerName() { 49 | return player.getServer().getInfo().getName(); 50 | } 51 | 52 | @Override 53 | public boolean isConnected() { 54 | return player.isConnected(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bungee/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: 'PAPIProxyBridge' 2 | version: '${version}' 3 | description: '${description}' 4 | author: 'William278' 5 | website: 'https://william278.net/' 6 | main: 'net.william278.papiproxybridge.BungeePAPIProxyBridge' -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | } 4 | 5 | java { 6 | toolchain { 7 | languageVersion = JavaLanguageVersion.of(17) 8 | } 9 | } 10 | 11 | dependencies { 12 | api 'net.jodah:expiringmap:0.5.11' 13 | 14 | compileOnly 'com.google.guava:guava:33.4.6-jre' 15 | compileOnly 'org.jetbrains:annotations:26.0.2' 16 | 17 | compileOnly 'net.kyori:adventure-api:4.19.0' 18 | compileOnly "net.kyori:adventure-text-serializer-gson:4.19.0" 19 | compileOnly 'io.lettuce:lettuce-core:6.5.5.RELEASE' 20 | compileOnly 'de.exlll:configlib-yaml:4.5.0' 21 | } -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/PAPIProxyBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge; 21 | 22 | import com.google.common.io.ByteArrayDataInput; 23 | import com.google.common.io.ByteStreams; 24 | import de.exlll.configlib.YamlConfigurations; 25 | import net.william278.papiproxybridge.config.Settings; 26 | import net.william278.papiproxybridge.messenger.Messenger; 27 | import net.william278.papiproxybridge.user.OnlineUser; 28 | import net.william278.papiproxybridge.user.Request; 29 | import org.jetbrains.annotations.NotNull; 30 | 31 | import java.io.*; 32 | import java.nio.file.Path; 33 | import java.util.*; 34 | import java.util.concurrent.CompletableFuture; 35 | import java.util.logging.Level; 36 | 37 | public interface PAPIProxyBridge { 38 | 39 | String HANDSHAKE_PLACEHOLDER = "%papiproxybridge_handshake%"; 40 | String HANDSHAKE_RESPONSE = "confirmed"; 41 | String NAMESPACE = "papiproxybridge"; 42 | String REQUEST = "request"; 43 | String RESPONSE = "response"; 44 | String FORMAT_CHANNEL = "format"; 45 | String COMPONENT_CHANNEL = "component"; 46 | 47 | @NotNull 48 | static String getChannel(boolean isRequest) { 49 | return getChannelNamespace() + ":" + subChannel(isRequest); 50 | } 51 | 52 | @NotNull 53 | static String getComponentChannel(boolean isRequest) { 54 | return getChannelNamespace() + ":" + subComponentChannel(isRequest); 55 | } 56 | 57 | static String subChannel(boolean isRequest) { 58 | return getChannelKey() + "-" + (isRequest ? REQUEST : RESPONSE); 59 | } 60 | 61 | static String subComponentChannel(boolean isRequest) { 62 | return getComponentChannelKey() + "-" + (isRequest ? REQUEST : RESPONSE); 63 | } 64 | 65 | @NotNull 66 | static String getChannelNamespace() { 67 | return NAMESPACE; 68 | } 69 | 70 | @NotNull 71 | static String getChannelKey() { 72 | return FORMAT_CHANNEL; 73 | } 74 | 75 | @NotNull 76 | static String getComponentChannelKey() { 77 | return COMPONENT_CHANNEL; 78 | } 79 | 80 | default String getLoadMessage() { 81 | return "PAPIProxyBridge (" + getServerType() + " " + getVersion() + ") has been enabled!"; 82 | } 83 | 84 | String getServerType(); 85 | 86 | default String getVersion() { 87 | return getClass().getPackage().getImplementationVersion(); 88 | } 89 | 90 | @NotNull 91 | Collection getOnlineUsers(); 92 | 93 | Optional findPlayer(@NotNull UUID uuid); 94 | 95 | default void handleMessage(@NotNull PAPIProxyBridge plugin, @NotNull String channel, byte[] message, boolean isRequest) { 96 | if (!channel.equals(PAPIProxyBridge.getChannel(isRequest)) && !channel.equals(getComponentChannel(isRequest))) { 97 | return; 98 | } 99 | 100 | final ByteArrayDataInput inputStream = ByteStreams.newDataInput(message); 101 | final long mostSignificantBits = inputStream.readLong(); 102 | final long leastSignificantBits = inputStream.readLong(); 103 | final UUID uuid = new UUID(mostSignificantBits, leastSignificantBits); 104 | final OnlineUser user = plugin.findPlayer(uuid).orElse(null); 105 | if (user == null) { 106 | return; 107 | } 108 | 109 | try { 110 | final short messageLength = inputStream.readShort(); 111 | final byte[] messageBody = new byte[messageLength]; 112 | inputStream.readFully(messageBody); 113 | user.handleMessage(plugin, Request.deserialize(messageBody), channel.equals(getComponentChannel(isRequest))); 114 | } catch (IOException | ClassNotFoundException | IllegalStateException e) { 115 | plugin.log(Level.SEVERE, "Failed to fully read plugin message. Is PAPIProxyBridge up-to-date and installed on all servers?", e); 116 | } 117 | } 118 | 119 | CompletableFuture createRequest(@NotNull String text, @NotNull OnlineUser requester, @NotNull UUID formatFor, boolean wantsJson, long requestTimeout); 120 | 121 | @Deprecated(since = "1.6", forRemoval = true) 122 | default CompletableFuture> findServers(long requestTimeout) { 123 | return getServers(requestTimeout).thenApply(ArrayList::new); 124 | } 125 | 126 | CompletableFuture> getServers(long requestTimeout); 127 | 128 | void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... exceptions); 129 | 130 | File getDataFolder(); 131 | 132 | default void loadConfig() { 133 | final Path settingsFile = getDataFolder().toPath().resolve("settings.yml"); 134 | final Settings settings = YamlConfigurations.update(settingsFile, Settings.class); 135 | setSettings(settings); 136 | } 137 | 138 | void setSettings(Settings settings); 139 | 140 | void loadMessenger(); 141 | 142 | Messenger getMessenger(); 143 | 144 | Settings getSettings(); 145 | } 146 | -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/api/PlaceholderAPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.api; 21 | 22 | import com.google.common.collect.MapMaker; 23 | import com.google.common.collect.Maps; 24 | import net.jodah.expiringmap.ExpiringMap; 25 | import net.kyori.adventure.text.Component; 26 | import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 27 | import net.william278.papiproxybridge.PAPIProxyBridge; 28 | import net.william278.papiproxybridge.config.Settings; 29 | import net.william278.papiproxybridge.user.OnlineUser; 30 | import org.jetbrains.annotations.ApiStatus; 31 | import org.jetbrains.annotations.NotNull; 32 | 33 | import java.util.*; 34 | import java.util.concurrent.*; 35 | import java.util.logging.Level; 36 | 37 | /** 38 | * The main API for the ProxyPlaceholderAPI plugin 39 | *

40 | * Use {@link #getInstance()} to get the instance of this class, 41 | * then use {@link #formatPlaceholders(String, OnlineUser)} to get a future that will supply the formatted text 42 | *

43 | * Example: 44 | *

{@code
 45 |  *     final PlaceholderAPI api = PlaceholderAPI.getInstance();
 46 |  *     final UUID player = player.getUniqueId();
 47 |  *     api.formatPlaceholders("Hello %player_name%!", player).thenAccept(formatted -> {
 48 |  *         player.sendMessage(formatted);
 49 |  *     });
 50 |  * }
51 | * 52 | * @author William278 53 | */ 54 | @SuppressWarnings("unused") 55 | public final class PlaceholderAPI { 56 | private final static Set instances = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); 57 | private static PAPIProxyBridge plugin; 58 | private final static ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, r -> new Thread(r, "PAPIProxyBridge-PlaceholderAPI-ScheduledThread")); 59 | private final static String PLACEHOLDER_DELIMITER = "%%%-%%%"; 60 | private final ConcurrentMap> cache; 61 | private final ConcurrentMap> componentCache; 62 | private long requestTimeout = 1000; 63 | private long cacheExpiry = 30000; 64 | private int retryTimes = 3; 65 | private long lastError = 0; 66 | 67 | /** 68 | * Internal only - Create a new instance of the API 69 | */ 70 | @ApiStatus.Internal 71 | private PlaceholderAPI() { 72 | this.cache = Maps.newConcurrentMap(); 73 | this.componentCache = Maps.newConcurrentMap(); 74 | } 75 | 76 | /** 77 | * Get the instance of the API. This is an entry point for the API. 78 | *

79 | * Shares expiration settings with all other plugins using this instance. 80 | * Prefer {@link #createInstance()} for unique instance customization. 81 | * 82 | * @return An instance of the API 83 | * @apiNote From version 1.3, #getInstance() will return a new instance of the PlaceholderAPI rather than a singleton 84 | * @since 1.0 85 | * @deprecated Use {@link #createInstance()} instead 86 | */ 87 | @Deprecated(since = "1.3") 88 | @NotNull 89 | public static PlaceholderAPI getInstance() { 90 | return createInstance(); 91 | } 92 | 93 | /** 94 | * Create a new instance of PlaceholderAPI allowing unique customization of caching mechanisms 95 | * 96 | * @return PlaceholderAPI instance that can be used to format text 97 | * @since 1.3 98 | */ 99 | @NotNull 100 | public static PlaceholderAPI createInstance() { 101 | final PlaceholderAPI instance = new PlaceholderAPI(); 102 | instances.add(instance); 103 | return instance; 104 | } 105 | 106 | /** 107 | * Internal only - Register the plugin with the API 108 | * 109 | * @param plugin The plugin to register 110 | */ 111 | @ApiStatus.Internal 112 | public static void register(@NotNull PAPIProxyBridge plugin) { 113 | PlaceholderAPI.plugin = plugin; 114 | } 115 | 116 | @ApiStatus.Internal 117 | public static void clearCache(@NotNull UUID player) { 118 | instances.forEach(instance -> { 119 | instance.cache.remove(player); 120 | instance.componentCache.remove(player); 121 | }); 122 | } 123 | 124 | @SuppressWarnings("unchecked") 125 | private static CompletableFuture orTimeoutAsync(CompletableFuture future, long timeout) { 126 | final CompletableFuture timeoutFuture = new CompletableFuture<>(); 127 | timeoutFuture.exceptionally(t -> { 128 | if (t instanceof CancellationException) { 129 | future.cancel(true); 130 | } 131 | return null; 132 | }); 133 | 134 | SCHEDULER.schedule(() -> { 135 | final TimeoutException timeoutException = new TimeoutException("Timeout reached"); 136 | timeoutFuture.completeExceptionally(timeoutException); 137 | future.completeExceptionally(timeoutException); 138 | }, timeout, TimeUnit.MILLISECONDS); 139 | return CompletableFuture.anyOf(future, timeoutFuture).thenApply(o -> (T) o); 140 | } 141 | 142 | /** 143 | * Format the text with the placeholders of the player 144 | *

145 | * This method accepts the {@link UUID unique id} of a player who will be passed as the user for formatting the placeholders; 146 | * distinct from the player dispatching the plugin message across the network. 147 | * 148 | * @param text The text to format 149 | * @param requester The player used to request the formatting 150 | * @param formatFor The player to format the text for 151 | * @return A future that will supply the formatted text 152 | * @since 1.2 153 | */ 154 | public CompletableFuture formatPlaceholders(@NotNull String text, @NotNull OnlineUser requester, 155 | @NotNull UUID formatFor) { 156 | return formatPlaceholders(text, requester, formatFor, retryTimes); 157 | } 158 | 159 | private CompletableFuture formatPlaceholders(@NotNull String text, @NotNull OnlineUser requester, 160 | @NotNull UUID formatFor, int times) { 161 | if (!requester.isConnected()) { 162 | return CompletableFuture.completedFuture(text); 163 | } 164 | if (cacheExpiry > 0 && cache.containsKey(formatFor) && cache.get(formatFor).containsKey(text)) { 165 | return CompletableFuture.completedFuture(cache.get(formatFor).get(text)); 166 | } 167 | final CompletableFuture future = plugin.createRequest(text, requester, formatFor, false, requestTimeout); 168 | return orTimeoutAsync(future, requestTimeout).thenApply(formatted -> { 169 | cache.computeIfAbsent(requester.getUniqueId(), uuid -> ExpiringMap.builder() 170 | .expiration(cacheExpiry, TimeUnit.MILLISECONDS) 171 | .build()) 172 | .put(text, formatted); 173 | return formatted; 174 | }).exceptionally(e -> { 175 | if (!requester.isConnected()) { 176 | return text; 177 | } 178 | 179 | if (checkLastError()) { 180 | return text; 181 | } 182 | 183 | // Handle failed to format exceptions 184 | if (e instanceof CompletionException || Arrays.stream(e.getSuppressed()) 185 | .anyMatch(TimeoutException.class::isInstance)) { 186 | plugin.log(Level.WARNING, ("Timed out formatting placeholders for %s after %sms." + 187 | "Is PAPIProxyBridge up-to-date and installed on all backend servers?") 188 | .formatted(requester.getUsername(), getRequestTimeout())); 189 | } else { 190 | plugin.log(Level.WARNING, "Failed to format placeholders for %s".formatted(requester.getUsername()), e); 191 | } 192 | 193 | return text; 194 | }); 195 | } 196 | 197 | /** 198 | * Format the text with the placeholders of the player 199 | * 200 | * @param text The text to format 201 | * @param player The player to format the text for 202 | * @return A future that will supply the formatted text 203 | * @since 1.0 204 | */ 205 | public CompletableFuture formatPlaceholders(@NotNull String text, @NotNull OnlineUser player) { 206 | return formatPlaceholders(text, player, player.getUniqueId()); 207 | } 208 | 209 | /** 210 | * Format the text with the placeholders of the player 211 | *

212 | * This method accepts the {@link UUID unique id} of a player who will be passed as the user for formatting the placeholders; 213 | * distinct from the player dispatching the plugin message across the network. 214 | * 215 | * @param text The text to format 216 | * @param requester The {@link UUID unique id} of the player used to request the formatting. Note that this user must be online. 217 | * @param formatFor The {@link UUID unique id} of the player to format the text for 218 | * @return A future that will supply the formatted text. If the requester is not online, the original text will be returned 219 | * @since 1.2 220 | */ 221 | public CompletableFuture formatPlaceholders(@NotNull String text, @NotNull UUID requester, 222 | @NotNull UUID formatFor) { 223 | return plugin.findPlayer(requester) 224 | .map(onlineRequester -> formatPlaceholders(text, onlineRequester, formatFor)) 225 | .orElse(CompletableFuture.completedFuture(text)); 226 | } 227 | 228 | /** 229 | * Format the text with the placeholders of the player 230 | * 231 | * @param text The text to format 232 | * @param player The {@link UUID unique id} of the player to format the text for. Note that this user must be online. 233 | * @return A future that will supply the formatted text. If the player is not online, the original text will be returned 234 | * @since 1.0 235 | */ 236 | public CompletableFuture formatPlaceholders(@NotNull String text, @NotNull UUID player) { 237 | return plugin.findPlayer(player) 238 | .map(requester -> formatPlaceholders(text, requester, player)) 239 | .orElse(CompletableFuture.completedFuture(text)); 240 | } 241 | 242 | /** 243 | * Format the text with the placeholders of the player 244 | *

245 | * This method accepts the {@link UUID unique id} of a player who will be passed as the user for formatting the placeholders; 246 | * distinct from the player dispatching the plugin message across the network. 247 | * 248 | * @param text The text to format 249 | * @param requester The player used to request the formatting 250 | * @param formatFor The player to format the text for 251 | * @return A future that will supply the formatted component 252 | * @since 1.4 253 | */ 254 | public CompletableFuture formatComponentPlaceholders(@NotNull String text, @NotNull OnlineUser requester, 255 | @NotNull UUID formatFor) { 256 | return formatComponentPlaceholders(text, requester, formatFor, retryTimes); 257 | } 258 | 259 | private CompletableFuture formatComponentPlaceholders(@NotNull String text, @NotNull OnlineUser requester, 260 | @NotNull UUID formatFor, int times) { 261 | if (!requester.isConnected()) { 262 | return CompletableFuture.completedFuture(Component.text(text)); 263 | } 264 | if (cacheExpiry > 0 && componentCache.containsKey(formatFor) && componentCache.get(formatFor).containsKey(text)) { 265 | return CompletableFuture.completedFuture(componentCache.get(formatFor).get(text)); 266 | } 267 | final CompletableFuture future = plugin.createRequest(text, requester, formatFor, true, requestTimeout); 268 | return orTimeoutAsync(future, requestTimeout).thenApply(formatted -> { 269 | final Component deserialized = GsonComponentSerializer.gson().deserializeOr(formatted, Component.text(formatted)); 270 | componentCache.computeIfAbsent(requester.getUniqueId(), uuid -> ExpiringMap.builder() 271 | .expiration(cacheExpiry, TimeUnit.MILLISECONDS) 272 | .build()) 273 | .put(text, deserialized); 274 | return deserialized; 275 | }).exceptionally(e -> { 276 | if (!requester.isConnected()) { 277 | return Component.text(text); 278 | } 279 | 280 | if (checkLastError()) { 281 | return Component.text(text); 282 | } 283 | 284 | // Handle failed to format exceptions 285 | if (e instanceof CompletionException || Arrays.stream(e.getSuppressed()) 286 | .anyMatch(TimeoutException.class::isInstance)) { 287 | plugin.log(Level.WARNING, ("Timed out formatting placeholders for %s after %sms." + 288 | "Is PAPIProxyBridge up-to-date and installed on all backend servers?") 289 | .formatted(requester.getUsername(), getRequestTimeout())); 290 | } else { 291 | plugin.log(Level.WARNING, "Failed to format placeholders for %s".formatted(requester.getUsername()), e); 292 | } 293 | 294 | return Component.text(text); 295 | }); 296 | } 297 | 298 | /** 299 | * Format the text with the placeholders of the player 300 | * 301 | * @param text The text to format 302 | * @param player The player to format the text for 303 | * @return A future that will supply the formatted component 304 | * @since 1.4 305 | */ 306 | public CompletableFuture formatComponentPlaceholders(@NotNull String text, @NotNull OnlineUser player) { 307 | return formatComponentPlaceholders(text, player, player.getUniqueId()); 308 | } 309 | 310 | /** 311 | * Format the text with the placeholders of the player 312 | *

313 | * This method accepts the {@link UUID unique id} of a player who will be passed as the user for formatting the placeholders; 314 | * distinct from the player dispatching the plugin message across the network. 315 | * 316 | * @param text The text to format 317 | * @param requester The {@link UUID unique id} of the player used to request the formatting. Note that this user must be online. 318 | * @param formatFor The {@link UUID unique id} of the player to format the text for 319 | * @return A future that will supply the formatted component. If the requester is not online, the original text will be returned 320 | * @since 1.4 321 | */ 322 | public CompletableFuture formatComponentPlaceholders(@NotNull String text, @NotNull UUID requester, 323 | @NotNull UUID formatFor) { 324 | return plugin.findPlayer(requester) 325 | .map(onlineRequester -> formatComponentPlaceholders(text, onlineRequester, formatFor)) 326 | .orElse(CompletableFuture.completedFuture(Component.text(text))); 327 | } 328 | 329 | /** 330 | * Format the text with the placeholders of the player 331 | * 332 | * @param text The text to format 333 | * @param player The {@link UUID unique id} of the player to format the text for. Note that this user must be online. 334 | * @return A future that will supply the formatted component. If the player is not online, the original text will be returned 335 | * @since 1.4 336 | */ 337 | public CompletableFuture formatComponentPlaceholders(@NotNull String text, @NotNull UUID player) { 338 | return plugin.findPlayer(player) 339 | .map(requester -> formatComponentPlaceholders(text, requester, player)) 340 | .orElse(CompletableFuture.completedFuture(Component.text(text))); 341 | } 342 | 343 | 344 | /** 345 | * Fetch the list of backend servers with PAPIProxyBridge installed 346 | * 347 | * @return A future that will supply the list of backend servers 348 | * @throws UnsupportedOperationException If this method is called from a backend (Bukkit, Fabric) server 349 | * @apiNote This method can only be used from the proxy; it will throw an exception if called from a backend server 350 | * @since 1.3 351 | * @deprecated Use {@link #getServers()} instead 352 | */ 353 | @SuppressWarnings("removal") 354 | @Deprecated(since = "1.6", forRemoval = true) 355 | public CompletableFuture> findServers() throws UnsupportedOperationException { 356 | return plugin.findServers(requestTimeout); 357 | } 358 | 359 | /** 360 | * Fetch the list of backend servers with PAPIProxyBridge installed 361 | * 362 | * @return A future that will supply the list of backend servers 363 | * @throws UnsupportedOperationException If this method is called from a backend (Bukkit, Fabric) server 364 | * @apiNote This method can only be used from the proxy; it will throw an exception if called from a backend server 365 | * @since 1.6 366 | */ 367 | public CompletableFuture> getServers() throws UnsupportedOperationException { 368 | return plugin.getServers(requestTimeout); 369 | } 370 | 371 | /** 372 | * Set the timeout for requesting formatting from the proxy in milliseconds. 373 | * If a request is not completed within this time, the original text will be returned 374 | *

375 | * The default value is 400 milliseconds (0.4 seconds) 376 | * 377 | * @param requestTimeout The timeout for requesting formatting from the proxy in milliseconds 378 | * @throws IllegalArgumentException If the timeout is negative 379 | * @since 1.2 380 | */ 381 | public void setRequestTimeout(long requestTimeout) { 382 | if (requestTimeout < 0) { 383 | throw new IllegalArgumentException("Request timeout cannot be negative"); 384 | } 385 | this.requestTimeout = requestTimeout; 386 | } 387 | 388 | /** 389 | * Set the expiry time for the cache in milliseconds 390 | *

391 | * The default value is 30000 milliseconds (30 seconds) 392 | * 393 | * @param cacheExpiry The expiry time for the cache in milliseconds 394 | * @throws IllegalArgumentException If the expiry time is negative 395 | * @since 1.2 396 | */ 397 | public void setCacheExpiry(long cacheExpiry) { 398 | if (cacheExpiry < 0) { 399 | throw new IllegalArgumentException("Cache expiry cannot be negative"); 400 | } 401 | this.cacheExpiry = cacheExpiry; 402 | } 403 | 404 | /** 405 | * Returns the timeout for requesting formatting from the proxy in milliseconds. 406 | * If a request is not completed within this time, the original text will be returned. 407 | * 408 | * @return The timeout in milliseconds 409 | * @since 1.2 410 | */ 411 | public long getRequestTimeout() { 412 | return requestTimeout; 413 | } 414 | 415 | /** 416 | * Returns the expiry time for the cache in milliseconds. 417 | * 418 | * @return The expiry time for the cache in milliseconds 419 | * @since 1.2 420 | */ 421 | public long getCacheExpiry() { 422 | return cacheExpiry; 423 | } 424 | 425 | /** 426 | * Set the number of times to retry formatting placeholders if the request times out 427 | *

428 | * The default value is 3 429 | * 430 | * @param retryTimes The number of times to retry formatting placeholders if the request times out 431 | * @throws IllegalArgumentException If the number of retries is negative 432 | * @since 1.6 433 | */ 434 | public void setRetryTimes(int retryTimes) { 435 | if (retryTimes < 0) { 436 | throw new IllegalArgumentException("Retry times cannot be negative"); 437 | } 438 | this.retryTimes = retryTimes; 439 | } 440 | 441 | /** 442 | * Returns the number of times to retry formatting placeholders if the request times out 443 | * 444 | * @return The number of times to retry formatting placeholders if the request times out 445 | * @since 1.6 446 | */ 447 | public int getRetryTimes() { 448 | return retryTimes; 449 | } 450 | 451 | /** 452 | * Returns the messenger type 453 | * 454 | * @return The messenger type 455 | * @since 1.7.3 456 | */ 457 | public Settings.MessengerType getMessengerType() { 458 | return plugin.getSettings().getMessenger(); 459 | } 460 | 461 | private boolean checkLastError() { 462 | if (System.currentTimeMillis() - lastError < 10000) { 463 | return true; 464 | } 465 | 466 | lastError = System.currentTimeMillis(); 467 | return false; 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/config/Settings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.config; 21 | 22 | import com.google.common.collect.Lists; 23 | import de.exlll.configlib.Comment; 24 | import de.exlll.configlib.Configuration; 25 | import lombok.AccessLevel; 26 | import lombok.Getter; 27 | import lombok.NoArgsConstructor; 28 | 29 | import java.util.List; 30 | 31 | @Getter 32 | @Configuration 33 | @SuppressWarnings({"unused", "FieldMayBeFinal"}) 34 | public class Settings { 35 | 36 | @Comment("The messenger to use for sending plugin messages. Options are PLUGIN_MESSAGE or REDIS.") 37 | private MessengerType messenger = MessengerType.PLUGIN_MESSAGE; 38 | private RedisSettings redis = new RedisSettings(); 39 | 40 | public enum MessengerType { 41 | PLUGIN_MESSAGE, 42 | REDIS 43 | } 44 | 45 | @Getter 46 | @Configuration 47 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 48 | public static class RedisSettings { 49 | 50 | @Comment("Specify the credentials of your Redis server here. Set \"password\" to '' if you don't have one") 51 | private RedisCredentials credentials = new RedisCredentials(); 52 | 53 | @Getter 54 | @Configuration 55 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 56 | public static class RedisCredentials { 57 | private String host = "localhost"; 58 | private int port = 6379; 59 | private String password = ""; 60 | private boolean useSsl = false; 61 | } 62 | 63 | @Comment("Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!") 64 | private RedisSentinel sentinel = new RedisSentinel(); 65 | 66 | @Getter 67 | @Configuration 68 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 69 | public static class RedisSentinel { 70 | @Comment("The master set name for the Redis sentinel.") 71 | private String master = ""; 72 | @Comment("List of host:port pairs") 73 | private List nodes = Lists.newArrayList(); 74 | private String password = ""; 75 | } 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/messenger/Messenger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger; 21 | 22 | import org.jetbrains.annotations.NotNull; 23 | 24 | import java.util.UUID; 25 | 26 | public abstract class Messenger { 27 | 28 | public void onEnable() { 29 | 30 | } 31 | 32 | public void onDisable() { 33 | 34 | } 35 | 36 | public abstract void sendMessage(@NotNull UUID uuid, @NotNull String channel, byte @NotNull [] message); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/messenger/redis/RedisMessenger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger.redis; 21 | 22 | import io.lettuce.core.RedisClient; 23 | import io.lettuce.core.RedisURI; 24 | import io.lettuce.core.api.StatefulRedisConnection; 25 | import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; 26 | import net.william278.papiproxybridge.PAPIProxyBridge; 27 | import net.william278.papiproxybridge.config.Settings; 28 | import net.william278.papiproxybridge.messenger.Messenger; 29 | import org.jetbrains.annotations.NotNull; 30 | 31 | import java.util.UUID; 32 | import java.util.logging.Level; 33 | 34 | public class RedisMessenger extends Messenger { 35 | 36 | private static final String CLIENT_NAME = "PAPIProxyBridge"; 37 | 38 | private final PAPIProxyBridge plugin; 39 | private final boolean isRequest; 40 | private final Settings.RedisSettings redisSettings; 41 | private RedisClient client; 42 | private StatefulRedisConnection connection; 43 | private boolean closed = false; 44 | 45 | /** 46 | * Create a new instance of the RedisMessenger 47 | * 48 | * @param plugin the plugin instance 49 | * @param redisSettings the redis settings 50 | * @param isRequest whether the messenger is for requests (backends) or responses (proxy) 51 | */ 52 | public RedisMessenger(@NotNull PAPIProxyBridge plugin, @NotNull Settings.RedisSettings redisSettings, boolean isRequest) { 53 | this.plugin = plugin; 54 | this.redisSettings = redisSettings; 55 | this.isRequest = isRequest; 56 | } 57 | 58 | @Override 59 | public void onEnable() { 60 | try { 61 | createClient(); 62 | } catch (Throwable e) { 63 | plugin.log(Level.SEVERE, "Failed to establish connection with Redis. " 64 | + "Please check the supplied credentials in the config file", e); 65 | return; 66 | } 67 | 68 | connection = client.connect(StringByteArrayCodec.INSTANCE); 69 | listen(); 70 | } 71 | 72 | private void createClient() { 73 | final Settings.RedisSettings.RedisCredentials credentials = redisSettings.getCredentials(); 74 | final Settings.RedisSettings.RedisSentinel sentinel = redisSettings.getSentinel(); 75 | 76 | if (sentinel.getNodes().isEmpty()) { 77 | client = RedisClient.create(RedisURI.builder() 78 | .withHost(credentials.getHost()) 79 | .withPort(credentials.getPort()) 80 | .withPassword(credentials.getPassword() == null ? null : credentials.getPassword().toCharArray()) 81 | .withClientName(CLIENT_NAME) 82 | .withSsl(credentials.isUseSsl()) 83 | .build()); 84 | return; 85 | } 86 | 87 | plugin.log(Level.INFO, "Connecting with redis sentinel"); 88 | final RedisURI.Builder builder = RedisURI.builder() 89 | .withSentinelMasterId(sentinel.getMaster()); 90 | 91 | sentinel.getNodes().forEach(node -> { 92 | final String[] split = node.split(":"); 93 | if (split.length != 2) { 94 | throw new IllegalArgumentException("Invalid sentinel node: " + node); 95 | } 96 | builder.withSentinel(split[0], Integer.parseInt(split[1])); 97 | }); 98 | 99 | builder.withClientName(CLIENT_NAME) 100 | .withSsl(credentials.isUseSsl()); 101 | client = RedisClient.create(builder.build()); 102 | } 103 | 104 | @Override 105 | public void onDisable() { 106 | try { 107 | client.close(); 108 | } catch (Throwable ignored) { 109 | 110 | } 111 | closed = true; 112 | } 113 | 114 | @Override 115 | public void sendMessage(@NotNull UUID uuid, @NotNull String channel, byte @NotNull [] message) { 116 | if (closed) { 117 | return; 118 | } 119 | connection.async().publish(channel, message); 120 | } 121 | 122 | public void listen() { 123 | final StatefulRedisPubSubConnection pubSubConnection = client.connectPubSub(StringByteArrayCodec.INSTANCE); 124 | pubSubConnection.addListener(new RedisPubSubListener() { 125 | @Override 126 | public void message(String string, byte[] bytes) { 127 | if (closed) { 128 | return; 129 | } 130 | plugin.handleMessage(plugin, string, bytes, isRequest); 131 | } 132 | }); 133 | 134 | pubSubConnection.async().subscribe(PAPIProxyBridge.getChannel(isRequest), PAPIProxyBridge.getComponentChannel(isRequest)); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/messenger/redis/RedisPubSubListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger.redis; 21 | 22 | public abstract class RedisPubSubListener implements io.lettuce.core.pubsub.RedisPubSubListener { 23 | 24 | @Override 25 | public void message(String string, String k1, byte[] bytes) { 26 | 27 | } 28 | 29 | @Override 30 | public void subscribed(String string, long l) { 31 | 32 | } 33 | 34 | @Override 35 | public void psubscribed(String string, long l) { 36 | 37 | } 38 | 39 | @Override 40 | public void unsubscribed(String string, long l) { 41 | 42 | } 43 | 44 | @Override 45 | public void punsubscribed(String string, long l) { 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/messenger/redis/StringByteArrayCodec.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger.redis; 21 | 22 | import io.lettuce.core.codec.RedisCodec; 23 | 24 | import java.nio.ByteBuffer; 25 | import java.nio.charset.Charset; 26 | import java.nio.charset.StandardCharsets; 27 | 28 | public class StringByteArrayCodec implements RedisCodec { 29 | 30 | public static final StringByteArrayCodec INSTANCE = new StringByteArrayCodec(); 31 | private static final byte[] EMPTY = new byte[0]; 32 | private final Charset charset = StandardCharsets.UTF_8; 33 | 34 | @Override 35 | public String decodeKey(final ByteBuffer bytes) { 36 | return charset.decode(bytes).toString(); 37 | } 38 | 39 | @Override 40 | public byte[] decodeValue(final ByteBuffer bytes) { 41 | return getBytes(bytes); 42 | } 43 | 44 | @Override 45 | public ByteBuffer encodeKey(final String key) { 46 | return charset.encode(key); 47 | } 48 | 49 | @Override 50 | public ByteBuffer encodeValue(final byte[] value) { 51 | if (value == null) { 52 | return ByteBuffer.wrap(EMPTY); 53 | } 54 | 55 | return ByteBuffer.wrap(value); 56 | } 57 | 58 | private static byte[] getBytes(final ByteBuffer buffer) { 59 | final byte[] b = new byte[buffer.remaining()]; 60 | buffer.get(b); 61 | return b; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/user/OnlineUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.user; 21 | 22 | import com.google.common.io.ByteArrayDataOutput; 23 | import com.google.common.io.ByteStreams; 24 | import net.william278.papiproxybridge.PAPIProxyBridge; 25 | import org.jetbrains.annotations.NotNull; 26 | 27 | import java.io.IOException; 28 | import java.util.UUID; 29 | import java.util.logging.Level; 30 | 31 | public interface OnlineUser { 32 | 33 | @NotNull 34 | String getUsername(); 35 | 36 | @NotNull 37 | UUID getUniqueId(); 38 | 39 | default void sendMessage(@NotNull PAPIProxyBridge plugin, @NotNull Request request, boolean wantsJson, boolean isRequest) { 40 | final ByteArrayDataOutput messageWriter = ByteStreams.newDataOutput(); 41 | final UUID uuid = getUniqueId(); 42 | messageWriter.writeLong(uuid.getMostSignificantBits()); // UUID - Most Significant Bits 43 | messageWriter.writeLong(uuid.getLeastSignificantBits()); // UUID - Least Significant Bits 44 | 45 | try { 46 | final byte[] serializedRequest = request.serialize(); 47 | messageWriter.writeShort(serializedRequest.length); 48 | messageWriter.write(serializedRequest); 49 | } catch (IOException e) { 50 | plugin.log(Level.SEVERE, "Exception serializing request: " + request, e); 51 | return; 52 | } 53 | 54 | plugin.getMessenger().sendMessage(request.getFormatFor(), wantsJson ? PAPIProxyBridge.getComponentChannel(isRequest) : PAPIProxyBridge.getChannel(isRequest), messageWriter.toByteArray()); 55 | } 56 | 57 | void handleMessage(@NotNull PAPIProxyBridge plugin, @NotNull Request message, boolean wantsJson); 58 | 59 | default boolean isConnected() { 60 | return true; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /common/src/main/java/net/william278/papiproxybridge/user/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.user; 21 | 22 | import lombok.AllArgsConstructor; 23 | import lombok.Getter; 24 | import lombok.Setter; 25 | import org.jetbrains.annotations.NotNull; 26 | 27 | import java.io.*; 28 | import java.util.UUID; 29 | 30 | @AllArgsConstructor 31 | @Getter 32 | public final class Request { 33 | 34 | private static final short VERSION = 1; 35 | 36 | private final UUID uuid; 37 | private final UUID formatFor; 38 | @Setter 39 | private String message; 40 | 41 | public Request(@NotNull String message, @NotNull UUID formatFor) { 42 | this.uuid = UUID.randomUUID(); 43 | this.formatFor = formatFor; 44 | this.message = message; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return uuid.toString() + formatFor.toString() + message; 50 | } 51 | 52 | public byte @NotNull [] serialize() throws IOException { 53 | final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 54 | final DataOutputStream dataStream = new DataOutputStream(byteStream); 55 | 56 | dataStream.writeShort(VERSION); 57 | dataStream.writeLong(uuid.getMostSignificantBits()); 58 | dataStream.writeLong(uuid.getLeastSignificantBits()); 59 | dataStream.writeLong(formatFor.getMostSignificantBits()); 60 | dataStream.writeLong(formatFor.getLeastSignificantBits()); 61 | dataStream.writeUTF(message); 62 | 63 | return byteStream.toByteArray(); 64 | } 65 | 66 | @NotNull 67 | public static Request deserialize(byte @NotNull [] data) throws IOException, ClassNotFoundException { 68 | final ByteArrayInputStream byteStream = new ByteArrayInputStream(data); 69 | final DataInputStream dataStream = new DataInputStream(byteStream); 70 | 71 | final short version = dataStream.readShort(); 72 | if (version != VERSION) { 73 | throw new IllegalStateException("Invalid version: " + version + ". Make sure you are using the latest version of PapiProxyBridge on all servers."); 74 | } 75 | final UUID uuid = new UUID(dataStream.readLong(), dataStream.readLong()); 76 | final UUID formatFor = new UUID(dataStream.readLong(), dataStream.readLong()); 77 | final String message = dataStream.readUTF(); 78 | 79 | return new Request(uuid, formatFor, message); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /fabric/1.20.1/build.gradle: -------------------------------------------------------------------------------- 1 | java { 2 | toolchain { 3 | languageVersion = JavaLanguageVersion.of(17) 4 | } 5 | } -------------------------------------------------------------------------------- /fabric/1.20.1/gradle.properties: -------------------------------------------------------------------------------- 1 | essential.defaults.loom.mappings=net.fabricmc:yarn:1.20.1+build.10:v2 2 | 3 | fabric_loader_version=0.15.11 4 | fabric_api_version=0.92.2+1.20.1 5 | fabric_adventure_platform_version=5.9.0 6 | fabric_placeholder_api_version=2.1.4+1.20.1 -------------------------------------------------------------------------------- /fabric/1.20.1/src/main/java/net/william278/papiproxybridge/messenger/PluginMessageMessenger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger; 21 | 22 | import lombok.RequiredArgsConstructor; 23 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 24 | import net.minecraft.network.packet.Packet; 25 | import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; 26 | import net.william278.papiproxybridge.FabricPAPIProxyBridge; 27 | import net.william278.papiproxybridge.payload.ComponentPayload; 28 | import net.william278.papiproxybridge.payload.LiteralPayload; 29 | import net.william278.papiproxybridge.payload.TemplatePayload; 30 | import net.william278.papiproxybridge.user.FabricUser; 31 | import org.jetbrains.annotations.NotNull; 32 | 33 | import java.util.Optional; 34 | import java.util.UUID; 35 | 36 | @RequiredArgsConstructor 37 | public class PluginMessageMessenger extends Messenger { 38 | 39 | private final FabricPAPIProxyBridge plugin; 40 | 41 | @Override 42 | public void onEnable() { 43 | ServerPlayNetworking.registerGlobalReceiver(LiteralPayload.REQUEST_ID, 44 | (minecraftServer, serverPlayerEntity, 45 | serverPlayNetworkHandler, packetByteBuf, 46 | packetSender) -> plugin.handleMessage(plugin, LiteralPayload.REQUEST_ID.toString(), TemplatePayload.getWrittenBytes(packetByteBuf), true)); 47 | 48 | ServerPlayNetworking.registerGlobalReceiver(ComponentPayload.REQUEST_ID, 49 | (minecraftServer, serverPlayerEntity, 50 | serverPlayNetworkHandler, packetByteBuf, 51 | packetSender) -> plugin.handleMessage(plugin, ComponentPayload.REQUEST_ID.toString(), TemplatePayload.getWrittenBytes(packetByteBuf), true)); 52 | } 53 | 54 | @Override 55 | public void sendMessage(@NotNull UUID uuid, @NotNull String channel, byte @NotNull [] message) { 56 | final Optional optionalFabricUser = plugin.findPlayer(uuid); 57 | if (optionalFabricUser.isEmpty()) { 58 | return; 59 | } 60 | 61 | final FabricUser user = optionalFabricUser.get(); 62 | final TemplatePayload templatePayload = channel.equals(ComponentPayload.REQUEST_ID.toString()) ? 63 | new ComponentPayload(message, false) : 64 | new LiteralPayload(message, false); 65 | 66 | final Packet packet = new CustomPayloadS2CPacket(templatePayload.getPacketByteBuf()); 67 | user.player().networkHandler.sendPacket(packet); 68 | } 69 | } -------------------------------------------------------------------------------- /fabric/1.20.1/src/main/java/net/william278/papiproxybridge/payload/ComponentPayload.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.payload; 21 | 22 | import net.minecraft.util.Identifier; 23 | import net.william278.papiproxybridge.PAPIProxyBridge; 24 | 25 | public final class ComponentPayload extends TemplatePayload { 26 | 27 | public static final Identifier RESPONSE_ID = new Identifier(PAPIProxyBridge.getComponentChannel(false)); 28 | public static final Identifier REQUEST_ID = new Identifier(PAPIProxyBridge.getComponentChannel(true)); 29 | 30 | private final boolean isRequest; 31 | 32 | public ComponentPayload(byte[] bytes, boolean isRequest) { 33 | super(bytes); 34 | this.isRequest = isRequest; 35 | } 36 | 37 | public Identifier getId() { 38 | return isRequest ? REQUEST_ID : RESPONSE_ID; 39 | } 40 | } -------------------------------------------------------------------------------- /fabric/1.20.1/src/main/java/net/william278/papiproxybridge/payload/LiteralPayload.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.payload; 21 | 22 | import net.minecraft.util.Identifier; 23 | import net.william278.papiproxybridge.PAPIProxyBridge; 24 | 25 | public final class LiteralPayload extends TemplatePayload { 26 | 27 | public static final Identifier REQUEST_ID = new Identifier(PAPIProxyBridge.getChannel(true)); 28 | public static final Identifier RESPONSE_ID = new Identifier(PAPIProxyBridge.getChannel(false)); 29 | 30 | private final boolean isRequest; 31 | 32 | public LiteralPayload(byte[] bytes, boolean isRequest) { 33 | super(bytes); 34 | this.isRequest = isRequest; 35 | } 36 | 37 | @Override 38 | Identifier getId() { 39 | return isRequest ? REQUEST_ID : RESPONSE_ID; 40 | } 41 | } -------------------------------------------------------------------------------- /fabric/1.20.1/src/main/java/net/william278/papiproxybridge/payload/TemplatePayload.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.payload; 21 | 22 | import io.netty.buffer.Unpooled; 23 | import lombok.Getter; 24 | import lombok.RequiredArgsConstructor; 25 | import net.minecraft.network.PacketByteBuf; 26 | import net.minecraft.util.Identifier; 27 | 28 | @Getter 29 | @RequiredArgsConstructor 30 | public abstract class TemplatePayload { 31 | 32 | protected final byte[] bytes; 33 | 34 | public static byte[] getWrittenBytes(PacketByteBuf buf) { 35 | return buf.getWrittenBytes(); 36 | } 37 | 38 | protected static void writeBytes(PacketByteBuf buf, byte[] v) { 39 | buf.writeBytes(v); 40 | } 41 | 42 | public PacketByteBuf getPacketByteBuf() { 43 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); 44 | buf.writeIdentifier(getId()); 45 | writeBytes(buf, bytes); 46 | return buf; 47 | } 48 | 49 | abstract Identifier getId(); 50 | } -------------------------------------------------------------------------------- /fabric/1.20.1/src/main/java/net/william278/papiproxybridge/user/FabricUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.user; 21 | 22 | import net.kyori.adventure.text.Component; 23 | import net.kyori.adventure.text.TranslatableComponent; 24 | import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 25 | import net.minecraft.server.network.ServerPlayerEntity; 26 | import net.minecraft.text.Text; 27 | import net.minecraft.util.Language; 28 | import net.william278.papiproxybridge.FabricPAPIProxyBridge; 29 | import net.william278.papiproxybridge.PAPIProxyBridge; 30 | import org.jetbrains.annotations.NotNull; 31 | import org.jetbrains.annotations.Nullable; 32 | 33 | import java.util.*; 34 | 35 | public record FabricUser(ServerPlayerEntity player) implements OnlineUser { 36 | 37 | @NotNull 38 | public static FabricUser adapt(@NotNull ServerPlayerEntity player) { 39 | return new FabricUser(player); 40 | } 41 | 42 | @Override 43 | @NotNull 44 | public String getUsername() { 45 | return player.getName().getString(); 46 | } 47 | 48 | @Override 49 | @NotNull 50 | public UUID getUniqueId() { 51 | return player.getUuid(); 52 | } 53 | 54 | private Component getComponent(Text text) { 55 | return GsonComponentSerializer.gson().deserialize(Text.Serializer.toJson(text)); 56 | } 57 | 58 | private Component translateKeys(TranslatableComponent translatable) { 59 | final String key = translatable.key(); 60 | final @Nullable String translated = Objects.requireNonNullElse( 61 | Language.getInstance().get(key, translatable.fallback()), 62 | key 63 | ); 64 | return translatable.fallback(translated); 65 | } 66 | 67 | @Override 68 | public void handleMessage(@NotNull PAPIProxyBridge plugin, @NotNull Request message, boolean wantsJson) { 69 | FabricPAPIProxyBridge bridge = (FabricPAPIProxyBridge) plugin; 70 | Text formatted = bridge.formatPlaceholders(message.getFormatFor(), this, message.getMessage()); 71 | Component original = getComponent(formatted); 72 | Component transformed = original.children().stream().map(component -> { 73 | if (component instanceof TranslatableComponent trans) { 74 | return translateKeys(trans); 75 | } 76 | return component; 77 | }).collect(Component.toComponent()).mergeStyle(original); 78 | String response = wantsJson ? GsonComponentSerializer.gson().serialize(transformed) : formatted.getString(); 79 | message.setMessage(response); 80 | this.sendMessage(plugin, message, wantsJson, false); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /fabric/1.21.1/gradle.properties: -------------------------------------------------------------------------------- 1 | essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.1+build.3:v2 2 | 3 | fabric_loader_version=0.16.10 4 | fabric_api_version=0.115.4+1.21.1 5 | fabric_adventure_platform_version=6.1.0 6 | fabric_placeholder_api_version=2.4.2+1.21 -------------------------------------------------------------------------------- /fabric/1.21.4/gradle.properties: -------------------------------------------------------------------------------- 1 | essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.4+build.8:v2 2 | 3 | fabric_loader_version=0.16.10 4 | fabric_api_version=0.116.1+1.21.4 5 | fabric_adventure_platform_version=6.3.0 6 | fabric_placeholder_api_version=2.5.2+1.21.3 -------------------------------------------------------------------------------- /fabric/1.21.5/gradle.properties: -------------------------------------------------------------------------------- 1 | essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.5+build.1:v2 2 | 3 | fabric_loader_version=0.16.14 4 | fabric_api_version=0.122.0+1.21.5 5 | fabric_adventure_platform_version=6.4.0-SNAPSHOT 6 | fabric_placeholder_api_version=2.6.3+1.21.5 -------------------------------------------------------------------------------- /fabric/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'gg.essential.multi-version' 3 | id 'gg.essential.defaults' 4 | } 5 | 6 | loom.serverOnlyMinecraftJar() 7 | 8 | repositories { 9 | maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } 10 | maven { url 'https://maven.nucleoid.xyz' } // PlaceholderAPI 11 | } 12 | 13 | dependencies { 14 | shadow implementation ('io.lettuce:lettuce-core:6.5.5.RELEASE') 15 | shadow implementation ('de.exlll:configlib-yaml:4.5.0') 16 | 17 | modImplementation include("net.kyori:adventure-platform-fabric:${fabric_adventure_platform_version}") 18 | modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}" 19 | modImplementation include("eu.pb4:placeholder-api:${fabric_placeholder_api_version}") 20 | 21 | compileOnly "net.kyori:adventure-text-serializer-gson:4.19.0" 22 | 23 | shadow project(path: ":common") 24 | } 25 | 26 | processResources { 27 | filesMatching(Arrays.asList("fabric.mod.json")) { 28 | expand([ 29 | version: version, 30 | fabric_loader_version: fabric_loader_version, 31 | fabric_minecraft_version: project.name, 32 | ]) 33 | } 34 | } 35 | 36 | shadowJar { 37 | configurations = [project.configurations.shadow] 38 | destinationDirectory.set(file("$projectDir/build/libs")) 39 | 40 | exclude('net.fabricmc:.*') 41 | exclude('net.kyori:.*') 42 | exclude '/mappings/*' 43 | 44 | relocate 'net.jodah', 'net.william278.papiproxybridge.libraries' 45 | relocate 'org.snakeyaml', 'net.william278.papiproxybridge.libraries.snakeyaml' 46 | relocate 'io.lettuce', 'net.william278.papiproxybridge.libraries.lettuce' 47 | relocate 'de.exlll', 'net.william278.papiproxybridge.libraries.configlib' 48 | relocate 'org.reactivestreams', 'net.william278.papiproxybridge.libraries.reactivestreams' 49 | relocate 'reactor', 'net.william278.papiproxybridge.libraries.reactor' 50 | 51 | minimize() 52 | } 53 | 54 | remapJar { 55 | dependsOn tasks.shadowJar 56 | mustRunAfter tasks.shadowJar 57 | inputFile = shadowJar.archiveFile.get() 58 | addNestedDependencies = true 59 | 60 | destinationDirectory.set(file("$rootDir/target/")) 61 | archiveClassifier.set('') 62 | } 63 | 64 | shadowJar.finalizedBy(remapJar) -------------------------------------------------------------------------------- /fabric/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=fabric 2 | 3 | essential.defaults.loom=1 4 | essential.defaults.loom.fabric-loader=net.fabricmc:fabric-loader:0.16.10 5 | 6 | org.gradle.daemon=false 7 | org.gradle.parallel=true 8 | org.gradle.configureoncommand=true 9 | org.gradle.parallel.threads=4 10 | org.gradle.jvmargs=-Xmx8G -------------------------------------------------------------------------------- /fabric/mainProject: -------------------------------------------------------------------------------- 1 | 1.21.5 -------------------------------------------------------------------------------- /fabric/root.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("gg.essential.multi-version.root") 3 | } 4 | 5 | preprocess { 6 | def fabric12105 = createNode("1.21.5", 12105, "yarn") 7 | def fabric12104 = createNode("1.21.4", 12104, "yarn") 8 | def fabric12101 = createNode("1.21.1", 12101, "yarn") 9 | def fabric12100 = createNode("1.20.1", 12100, "yarn") 10 | 11 | strictExtraMappings.set(true) 12 | fabric12104.link(fabric12105, null) 13 | fabric12101.link(fabric12105, null) 14 | fabric12100.link(fabric12105, null) 15 | } -------------------------------------------------------------------------------- /fabric/src/main/java/net/william278/papiproxybridge/FabricPAPIProxyBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge; 21 | 22 | import com.google.common.collect.Maps; 23 | import eu.pb4.placeholders.api.PlaceholderContext; 24 | import eu.pb4.placeholders.api.Placeholders; 25 | import lombok.Getter; 26 | import lombok.Setter; 27 | import net.fabricmc.api.DedicatedServerModInitializer; 28 | import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; 29 | import net.fabricmc.loader.api.FabricLoader; 30 | import net.minecraft.text.Text; 31 | import net.william278.papiproxybridge.api.PlaceholderAPI; 32 | import net.william278.papiproxybridge.config.Settings; 33 | import net.william278.papiproxybridge.messenger.Messenger; 34 | import net.william278.papiproxybridge.messenger.PluginMessageMessenger; 35 | import net.william278.papiproxybridge.messenger.redis.RedisMessenger; 36 | import net.william278.papiproxybridge.user.FabricUser; 37 | import net.william278.papiproxybridge.user.OnlineUser; 38 | import org.jetbrains.annotations.NotNull; 39 | import org.slf4j.Logger; 40 | import org.slf4j.LoggerFactory; 41 | 42 | import java.io.File; 43 | import java.util.*; 44 | import java.util.concurrent.CompletableFuture; 45 | import java.util.logging.Level; 46 | 47 | @Getter 48 | public class FabricPAPIProxyBridge implements DedicatedServerModInitializer, PAPIProxyBridge { 49 | 50 | public static final Logger LOGGER = LoggerFactory.getLogger("FabricPAPIProxyBridge"); 51 | private Map fabricUsers; 52 | @Setter 53 | private Settings settings; 54 | private Messenger messenger; 55 | 56 | @Override 57 | public void onInitializeServer() { 58 | fabricUsers = Maps.newConcurrentMap(); 59 | loadConfig(); 60 | loadMessenger(); 61 | messenger.onEnable(); 62 | 63 | PlaceholderAPI.register(this); 64 | 65 | handleEvents(); 66 | 67 | LOGGER.info(getLoadMessage()); 68 | } 69 | 70 | private void handleEvents() { 71 | ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { 72 | FabricUser user = FabricUser.adapt(handler.player); 73 | fabricUsers.put(user.getUniqueId(), user); 74 | }); 75 | ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> fabricUsers.remove(handler.player.getUuid())); 76 | } 77 | 78 | @Override 79 | public String getVersion() { 80 | return FabricLoader.getInstance().getModContainer("papiproxybridge") 81 | .map(container -> container.getMetadata().getVersion().getFriendlyString()) 82 | .orElse("Unknown"); 83 | } 84 | 85 | 86 | @Override 87 | public String getServerType() { 88 | return "Fabric"; 89 | } 90 | 91 | @Override 92 | @NotNull 93 | public Collection getOnlineUsers() { 94 | return fabricUsers.values(); 95 | } 96 | 97 | @Override 98 | public Optional findPlayer(@NotNull UUID uuid) { 99 | return Optional.ofNullable(fabricUsers.get(uuid)); 100 | } 101 | 102 | @Override 103 | public CompletableFuture createRequest(@NotNull String text, @NotNull OnlineUser requester, @NotNull UUID formatFor, boolean wantsJson, long requestTimeout) { 104 | String json = formatPlaceholders(formatFor, (FabricUser) requester, text).getString(); 105 | return CompletableFuture.completedFuture(json); 106 | } 107 | 108 | @Override 109 | public CompletableFuture> getServers(long requestTimeout) { 110 | throw new UnsupportedOperationException("Cannot fetch the list of servers from a backend Fabric server."); 111 | } 112 | 113 | @Override 114 | public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... exceptions) { 115 | if (exceptions.length > 0) { 116 | LOGGER.error(message, exceptions[0]); 117 | } else { 118 | LOGGER.info(message); 119 | } 120 | } 121 | 122 | @Override 123 | public File getDataFolder() { 124 | final File folder = FabricLoader.getInstance().getConfigDir().resolve("papiproxybridge").toFile(); 125 | if (!folder.exists() && !folder.mkdirs()) { 126 | throw new IllegalStateException("Failed to create data folder"); 127 | } 128 | 129 | return folder; 130 | } 131 | 132 | @Override 133 | public void loadMessenger() { 134 | switch (settings.getMessenger()) { 135 | case REDIS -> messenger = new RedisMessenger(this, settings.getRedis(), true); 136 | case PLUGIN_MESSAGE -> messenger = new PluginMessageMessenger(this); 137 | } 138 | 139 | log(Level.INFO, "Loaded messenger " + settings.getMessenger().name()); 140 | } 141 | 142 | @NotNull 143 | public final Text formatPlaceholders(@NotNull UUID formatFor, @NotNull FabricUser requester, @NotNull String text) { 144 | text = text.replaceAll(HANDSHAKE_PLACEHOLDER, HANDSHAKE_RESPONSE); 145 | return Placeholders.parseText(Text.of(text), PlaceholderContext.of( 146 | findPlayer(formatFor).orElse(requester).player()) 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /fabric/src/main/java/net/william278/papiproxybridge/messenger/PluginMessageMessenger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger; 21 | 22 | import lombok.RequiredArgsConstructor; 23 | import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; 24 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 25 | import net.minecraft.network.packet.CustomPayload; 26 | import net.minecraft.network.packet.Packet; 27 | import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; 28 | import net.william278.papiproxybridge.FabricPAPIProxyBridge; 29 | import net.william278.papiproxybridge.payload.ComponentPayload; 30 | import net.william278.papiproxybridge.payload.LiteralPayload; 31 | import net.william278.papiproxybridge.user.FabricUser; 32 | import org.jetbrains.annotations.NotNull; 33 | 34 | import java.util.Optional; 35 | import java.util.UUID; 36 | 37 | @RequiredArgsConstructor 38 | public class PluginMessageMessenger extends Messenger { 39 | 40 | private final FabricPAPIProxyBridge plugin; 41 | 42 | @Override 43 | public void onEnable() { 44 | PayloadTypeRegistry.playC2S().register(LiteralPayload.RESPONSE_ID, LiteralPayload.CODEC); 45 | PayloadTypeRegistry.playS2C().register(LiteralPayload.RESPONSE_ID, LiteralPayload.CODEC); 46 | PayloadTypeRegistry.playS2C().register(LiteralPayload.REQUEST_ID, LiteralPayload.CODEC); 47 | PayloadTypeRegistry.playC2S().register(LiteralPayload.REQUEST_ID, LiteralPayload.CODEC); 48 | PayloadTypeRegistry.playC2S().register(ComponentPayload.RESPONSE_ID, ComponentPayload.CODEC); 49 | PayloadTypeRegistry.playS2C().register(ComponentPayload.RESPONSE_ID, ComponentPayload.CODEC); 50 | PayloadTypeRegistry.playS2C().register(ComponentPayload.REQUEST_ID, ComponentPayload.CODEC); 51 | PayloadTypeRegistry.playC2S().register(ComponentPayload.REQUEST_ID, ComponentPayload.CODEC); 52 | 53 | ServerPlayNetworking.registerGlobalReceiver(LiteralPayload.REQUEST_ID, (payload, context) -> plugin.handleMessage(plugin, LiteralPayload.REQUEST_ID.id().toString(), payload.getBytes(), true)); 54 | ServerPlayNetworking.registerGlobalReceiver(ComponentPayload.REQUEST_ID, (payload, context) -> plugin.handleMessage(plugin, ComponentPayload.REQUEST_ID.id().toString(), payload.getBytes(), true)); 55 | } 56 | 57 | @Override 58 | public void sendMessage(@NotNull UUID uuid, @NotNull String channel, byte @NotNull [] message) { 59 | final Optional optionalFabricUser = plugin.findPlayer(uuid); 60 | if (optionalFabricUser.isEmpty()) { 61 | return; 62 | } 63 | final FabricUser user = optionalFabricUser.get(); 64 | 65 | final CustomPayload payload = channel.equals(ComponentPayload.RESPONSE_ID.id().toString()) ? 66 | new ComponentPayload(message, false) : 67 | new LiteralPayload(message, false); 68 | final Packet packet = new CustomPayloadS2CPacket(payload); 69 | user.player().networkHandler.sendPacket(packet); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /fabric/src/main/java/net/william278/papiproxybridge/payload/ComponentPayload.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.payload; 21 | 22 | import net.minecraft.network.PacketByteBuf; 23 | import net.minecraft.network.codec.PacketCodec; 24 | import net.minecraft.network.packet.CustomPayload; 25 | import net.minecraft.util.Identifier; 26 | import net.william278.papiproxybridge.PAPIProxyBridge; 27 | 28 | public final class ComponentPayload extends TemplatePayload { 29 | 30 | public static final CustomPayload.Id REQUEST_ID = new CustomPayload.Id<>(Identifier.of(PAPIProxyBridge.getComponentChannel(false))); 31 | public static final CustomPayload.Id RESPONSE_ID = new CustomPayload.Id<>(Identifier.of(PAPIProxyBridge.getComponentChannel(true))); 32 | public static final PacketCodec CODEC = PacketCodec.of((value, buf) -> writeBytes(buf, value.bytes), ComponentPayload::new); 33 | 34 | private final boolean isRequest; 35 | 36 | public ComponentPayload(byte[] bytes, boolean isRequest) { 37 | super(bytes); 38 | this.isRequest = isRequest; 39 | } 40 | 41 | private ComponentPayload(PacketByteBuf buf) { 42 | this(getWrittenBytes(buf), true); 43 | } 44 | 45 | @Override 46 | public Id getId() { 47 | return isRequest ? REQUEST_ID : RESPONSE_ID; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fabric/src/main/java/net/william278/papiproxybridge/payload/LiteralPayload.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.payload; 21 | 22 | import net.minecraft.network.PacketByteBuf; 23 | import net.minecraft.network.RegistryByteBuf; 24 | import net.minecraft.network.codec.PacketCodec; 25 | import net.minecraft.network.packet.CustomPayload; 26 | import net.minecraft.util.Identifier; 27 | import net.william278.papiproxybridge.PAPIProxyBridge; 28 | 29 | public final class LiteralPayload extends TemplatePayload { 30 | 31 | public static final Id REQUEST_ID = new Id<>(Identifier.of(PAPIProxyBridge.getChannel(true))); 32 | public static final Id RESPONSE_ID = new Id<>(Identifier.of(PAPIProxyBridge.getChannel(false))); 33 | public static final PacketCodec CODEC = PacketCodec.of((value, buf) -> writeBytes(buf, value.bytes), LiteralPayload::new); 34 | 35 | private final boolean isRequest; 36 | 37 | public LiteralPayload(byte[] bytes, boolean isRequest) { 38 | super(bytes); 39 | this.isRequest = isRequest; 40 | } 41 | 42 | private LiteralPayload(PacketByteBuf buf) { 43 | this(getWrittenBytes(buf), true); 44 | } 45 | 46 | @Override 47 | public Id getId() { 48 | return isRequest ? REQUEST_ID : RESPONSE_ID; 49 | } 50 | } -------------------------------------------------------------------------------- /fabric/src/main/java/net/william278/papiproxybridge/payload/TemplatePayload.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.payload; 21 | 22 | import lombok.Getter; 23 | import lombok.RequiredArgsConstructor; 24 | import net.minecraft.network.PacketByteBuf; 25 | import net.minecraft.network.packet.CustomPayload; 26 | 27 | @Getter 28 | @RequiredArgsConstructor 29 | public abstract class TemplatePayload implements CustomPayload { 30 | 31 | protected final byte[] bytes; 32 | 33 | protected static byte[] getWrittenBytes(PacketByteBuf buf) { 34 | byte[] bs = new byte[buf.readableBytes()]; 35 | buf.readBytes(bs); 36 | return bs; 37 | } 38 | 39 | protected static void writeBytes(PacketByteBuf buf, byte[] v) { 40 | buf.writeBytes(v); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /fabric/src/main/java/net/william278/papiproxybridge/user/FabricUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.user; 21 | 22 | import net.kyori.adventure.text.Component; 23 | import net.kyori.adventure.text.TranslatableComponent; 24 | import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 25 | import net.minecraft.registry.DynamicRegistryManager; 26 | import net.minecraft.server.network.ServerPlayerEntity; 27 | import net.minecraft.text.Text; 28 | import net.minecraft.util.Language; 29 | import net.william278.papiproxybridge.FabricPAPIProxyBridge; 30 | import net.william278.papiproxybridge.PAPIProxyBridge; 31 | import org.jetbrains.annotations.NotNull; 32 | import org.jetbrains.annotations.Nullable; 33 | 34 | import java.util.*; 35 | 36 | public record FabricUser(ServerPlayerEntity player) implements OnlineUser { 37 | 38 | @NotNull 39 | public static FabricUser adapt(@NotNull ServerPlayerEntity player) { 40 | return new FabricUser(player); 41 | } 42 | 43 | @Override 44 | @NotNull 45 | public String getUsername() { 46 | return player.getName().getString(); 47 | } 48 | 49 | @Override 50 | @NotNull 51 | public UUID getUniqueId() { 52 | return player.getUuid(); 53 | } 54 | 55 | private Component getComponent(Text text) { 56 | return GsonComponentSerializer.gson().deserialize(Text.Serialization.toJsonString(text, new DynamicRegistryManager.ImmutableImpl(List.of()))); 57 | } 58 | 59 | private Component translateKeys(TranslatableComponent translatable) { 60 | final String key = translatable.key(); 61 | final @Nullable String translated = Objects.requireNonNullElse( 62 | Language.getInstance().get(key, translatable.fallback()), 63 | key 64 | ); 65 | return translatable.fallback(translated); 66 | } 67 | 68 | @Override 69 | public void handleMessage(@NotNull PAPIProxyBridge plugin, @NotNull Request message, boolean wantsJson) { 70 | FabricPAPIProxyBridge bridge = (FabricPAPIProxyBridge) plugin; 71 | Text formatted = bridge.formatPlaceholders(message.getFormatFor(), this, message.getMessage()); 72 | Component original = getComponent(formatted); 73 | Component transformed = original.children().stream().map(component -> { 74 | if (component instanceof TranslatableComponent trans) { 75 | return translateKeys(trans); 76 | } 77 | return component; 78 | }).collect(Component.toComponent()).mergeStyle(original); 79 | String response = wantsJson ? GsonComponentSerializer.gson().serialize(transformed) : formatted.getString(); 80 | message.setMessage(response); 81 | this.sendMessage(plugin, message, wantsJson, false); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "papiproxybridge", 4 | "version": "${version}", 5 | "name": "PAPIProxyBridge", 6 | "description": "${description}", 7 | "authors": [ 8 | "WiIIiam278" 9 | ], 10 | "contributors": [ 11 | "Awakened Redstone" 12 | ], 13 | "contact": { 14 | "repo": "https://github.com/WiIIiam278/PAPIProxyBridge" 15 | }, 16 | "license": "Apache-2.0", 17 | "environment": "server", 18 | "entrypoints": { 19 | "server": [ 20 | "net.william278.papiproxybridge.FabricPAPIProxyBridge" 21 | ] 22 | }, 23 | "depends": { 24 | "fabricloader": ">=${fabric_loader_version}", 25 | "minecraft": "${fabric_minecraft_version}", 26 | "fabric-api": "*", 27 | "placeholder-api": "*" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs='-Dfile.encoding=UTF-8' 2 | org.gradle.daemon=true 3 | javaVersion=21 4 | 5 | plugin_version=1.8.1 6 | plugin_archive=papiproxybridge 7 | plugin_description=A bridge library plugin for using PlaceholderAPI on proxy servers 8 | 9 | # Fabric settings 10 | loom.ignoreDependencyLoomVersionValidation=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WiIIiam278/PAPIProxyBridge/3ca29f36001782ba60139d8085682b35eb0d53b7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | org.gradle.wrapper.GradleWrapperMain \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WiIIiam278/PAPIProxyBridge/3ca29f36001782ba60139d8085682b35eb0d53b7/images/banner.png -------------------------------------------------------------------------------- /proxy/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation project(path: ':common') 3 | 4 | compileOnly 'com.google.guava:guava:33.4.6-jre' 5 | compileOnly 'org.jetbrains:annotations:26.0.2' 6 | compileOnly 'io.lettuce:lettuce-core:6.5.5.RELEASE' 7 | compileOnly 'de.exlll:configlib-yaml:4.5.0' 8 | } 9 | 10 | shadowJar { 11 | relocate 'net.jodah', 'net.william278.papiproxybridge.libraries' 12 | 13 | minimize() 14 | } -------------------------------------------------------------------------------- /proxy/src/main/java/net/william278/papiproxybridge/ProxyPAPIProxyBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge; 21 | 22 | import com.google.common.collect.*; 23 | import net.william278.papiproxybridge.user.OnlineUser; 24 | import net.william278.papiproxybridge.user.ProxyUser; 25 | import net.william278.papiproxybridge.user.Request; 26 | import org.jetbrains.annotations.NotNull; 27 | 28 | import java.util.Map; 29 | import java.util.Set; 30 | import java.util.UUID; 31 | import java.util.concurrent.*; 32 | import java.util.stream.Collectors; 33 | 34 | public interface ProxyPAPIProxyBridge extends PAPIProxyBridge { 35 | 36 | @NotNull 37 | ConcurrentMap> getRequests(); 38 | 39 | default CompletableFuture createRequest(@NotNull String text, @NotNull OnlineUser requester, @NotNull UUID formatFor, 40 | boolean wantsJson, long requestTimeout) { 41 | final Request request = new Request(text, formatFor); 42 | final CompletableFuture future = new CompletableFuture<>(); 43 | getRequests().put(request.getUuid(), future); 44 | future.exceptionallyAsync(throwable -> { 45 | getRequests().remove(request.getUuid()); 46 | return text; 47 | }); 48 | requester.sendMessage(this, request, wantsJson, true); 49 | return future; 50 | } 51 | 52 | default CompletableFuture> getServers(long requestTimeout) { 53 | final CompletableFuture> future = new CompletableFuture<>(); 54 | final Multimap> serverMap = getOnlineUsers().stream() 55 | .filter(user -> user instanceof ProxyUser) 56 | .map(user -> (ProxyUser) user) 57 | .filter(OnlineUser::isConnected) 58 | .collect(() -> Multimaps.newSetMultimap(Maps.newConcurrentMap(), Sets::newConcurrentHashSet), 59 | (map, user) -> map.put(user.getServerName(), createRequest(HANDSHAKE_PLACEHOLDER, user, user.getUniqueId(), false, requestTimeout) 60 | .thenApply(message -> message.equals(HANDSHAKE_RESPONSE))), 61 | Multimap::putAll); 62 | 63 | CompletableFuture.allOf(serverMap.values().toArray(new CompletableFuture[0])) 64 | .thenRun(() -> { 65 | final Set servers = serverMap.asMap().entrySet().stream() 66 | .filter(entry -> entry.getValue().stream().anyMatch(CompletableFuture::join)) 67 | .map(Map.Entry::getKey) 68 | .collect(Collectors.toSet()); 69 | future.complete(Set.copyOf(servers)); 70 | }); 71 | 72 | return future; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /proxy/src/main/java/net/william278/papiproxybridge/user/ProxyUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.user; 21 | 22 | import net.william278.papiproxybridge.PAPIProxyBridge; 23 | import net.william278.papiproxybridge.ProxyPAPIProxyBridge; 24 | import org.jetbrains.annotations.NotNull; 25 | 26 | import java.util.UUID; 27 | import java.util.concurrent.CompletableFuture; 28 | import java.util.concurrent.ConcurrentMap; 29 | 30 | public interface ProxyUser extends OnlineUser { 31 | 32 | @Override 33 | default void handleMessage(@NotNull PAPIProxyBridge plugin, @NotNull Request message, boolean wantsJson) { 34 | final ConcurrentMap> requests = ((ProxyPAPIProxyBridge) plugin).getRequests(); 35 | CompletableFuture future = requests.get(message.getUuid()); 36 | 37 | if (future != null) { 38 | future.complete(message.getMessage()); 39 | requests.remove(message.getUuid()); 40 | } 41 | } 42 | 43 | @NotNull 44 | String getServerName(); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | maven { url 'https://maven.fabricmc.net/' } 5 | maven { url 'https://maven.architectury.dev/' } 6 | maven { url 'https://maven.minecraftforge.net' } 7 | maven { url 'https://repo.essential.gg/repository/maven-public' } 8 | } 9 | 10 | plugins { 11 | def egtVersion = "0.6.5" 12 | id("gg.essential.defaults") version egtVersion 13 | id("gg.essential.multi-version.root") version egtVersion 14 | } 15 | } 16 | 17 | rootProject.name = 'PAPIProxyBridge' 18 | 19 | include 'common' 20 | include 'bukkit' 21 | include 'proxy' 22 | include 'bungee' 23 | include 'velocity' 24 | 25 | // Fabric 26 | include("fabric") 27 | project(":fabric").with { 28 | projectDir = file("fabric/") 29 | buildFileName = "root.gradle" 30 | } 31 | 32 | file('fabric').listFiles((FileFilter) ((File file) -> file.isDirectory() && file.name ==~ /(\d+)\.(\d+)(\.(\d+))?/)).each { 33 | include("fabric:$it.name") 34 | project(":fabric:$it.name").with { 35 | projectDir = file("fabric/${it.name}") 36 | buildFileName = '../build.gradle' 37 | } 38 | } -------------------------------------------------------------------------------- /velocity/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation project(path: ':proxy') 3 | implementation project(path: ':common') 4 | 5 | implementation 'org.bstats:bstats-velocity:3.1.0' 6 | implementation 'io.lettuce:lettuce-core:6.5.5.RELEASE' 7 | implementation 'de.exlll:configlib-yaml:4.5.0' 8 | 9 | compileOnly 'com.velocitypowered:velocity-api:3.4.0-SNAPSHOT' 10 | compileOnly 'org.jetbrains:annotations:26.0.2' 11 | } 12 | 13 | shadowJar { 14 | relocate 'net.jodah', 'net.william278.papiproxybridge.libraries' 15 | relocate 'org.bstats', 'net.william278.papiproxybridge.libraries.bstats' 16 | relocate 'org.snakeyaml', 'net.william278.papiproxybridge.libraries.snakeyaml' 17 | relocate 'io.lettuce', 'net.william278.papiproxybridge.libraries.lettuce' 18 | relocate 'de.exlll', 'net.william278.papiproxybridge.libraries.configlib' 19 | relocate 'org.reactivestreams', 'net.william278.papiproxybridge.libraries.reactivestreams' 20 | relocate 'reactor', 'net.william278.papiproxybridge.libraries.reactor' 21 | 22 | dependencies { 23 | //noinspection GroovyAssignabilityCheck 24 | exclude dependency(':slf4j-api') 25 | exclude dependency('io.netty:') 26 | } 27 | 28 | minimize() 29 | } -------------------------------------------------------------------------------- /velocity/src/main/java/net/william278/papiproxybridge/VelocityPAPIProxyBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge; 21 | 22 | import com.google.common.collect.Maps; 23 | import com.google.inject.Inject; 24 | import com.velocitypowered.api.event.Subscribe; 25 | import com.velocitypowered.api.event.connection.DisconnectEvent; 26 | import com.velocitypowered.api.event.connection.PostLoginEvent; 27 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 28 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 29 | import com.velocitypowered.api.plugin.Plugin; 30 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 31 | import com.velocitypowered.api.proxy.Player; 32 | import com.velocitypowered.api.proxy.ProxyServer; 33 | import lombok.Getter; 34 | import lombok.Setter; 35 | import net.william278.papiproxybridge.api.PlaceholderAPI; 36 | import net.william278.papiproxybridge.config.Settings; 37 | import net.william278.papiproxybridge.messenger.Messenger; 38 | import net.william278.papiproxybridge.messenger.PluginMessageMessenger; 39 | import net.william278.papiproxybridge.messenger.redis.RedisMessenger; 40 | import net.william278.papiproxybridge.user.VelocityUser; 41 | import org.bstats.charts.SimplePie; 42 | import org.bstats.velocity.Metrics; 43 | import org.jetbrains.annotations.NotNull; 44 | import org.slf4j.Logger; 45 | 46 | import java.io.File; 47 | import java.nio.file.Path; 48 | import java.util.*; 49 | import java.util.concurrent.CompletableFuture; 50 | import java.util.concurrent.ConcurrentMap; 51 | import java.util.logging.Level; 52 | 53 | @Plugin(id = "papiproxybridge") 54 | @SuppressWarnings("unused") 55 | @Getter 56 | public class VelocityPAPIProxyBridge implements ProxyPAPIProxyBridge { 57 | 58 | private final ConcurrentMap> requests; 59 | 60 | private final ProxyServer server; 61 | private final Logger logger; 62 | private final Metrics.Factory metricsFactory; 63 | private final Path configDirectory; 64 | private final Map velocityUsers; 65 | @Setter 66 | private Settings settings; 67 | private Messenger messenger; 68 | 69 | @Inject 70 | public VelocityPAPIProxyBridge(ProxyServer server, org.slf4j.Logger logger, Metrics.Factory metricsFactory, @DataDirectory Path configDirectory) { 71 | this.server = server; 72 | this.logger = logger; 73 | this.metricsFactory = metricsFactory; 74 | this.configDirectory = configDirectory; 75 | this.requests = Maps.newConcurrentMap(); 76 | this.velocityUsers = Maps.newConcurrentMap(); 77 | } 78 | 79 | private void loadOnlineUsers() { 80 | velocityUsers.clear(); 81 | server.getAllPlayers().forEach(this::loadPlayer); 82 | } 83 | 84 | private void loadPlayer(@NotNull Player player) { 85 | final VelocityUser user = VelocityUser.adapt(player); 86 | velocityUsers.put(player.getUniqueId(), user); 87 | } 88 | 89 | @Subscribe 90 | public void onProxyInitialization(@NotNull ProxyInitializeEvent event) { 91 | loadConfig(); 92 | loadMessenger(); 93 | messenger.onEnable(); 94 | loadOnlineUsers(); 95 | 96 | 97 | // Register the plugin with the API 98 | PlaceholderAPI.register(this); 99 | 100 | // Setup metrics 101 | setupMetrics(); 102 | 103 | logger.info(getLoadMessage()); 104 | } 105 | 106 | @Subscribe 107 | public void onProxyShutdown(@NotNull ProxyShutdownEvent event) { 108 | messenger.onDisable(); 109 | } 110 | 111 | @Subscribe 112 | public void onConnect(PostLoginEvent event) { 113 | loadPlayer(event.getPlayer()); 114 | } 115 | 116 | @Subscribe 117 | public void onDisconnect(DisconnectEvent event) { 118 | if (event.getLoginStatus() == DisconnectEvent.LoginStatus.CONFLICTING_LOGIN) { 119 | return; 120 | } 121 | 122 | velocityUsers.remove(event.getPlayer().getUniqueId()); 123 | PlaceholderAPI.clearCache(event.getPlayer().getUniqueId()); 124 | } 125 | 126 | private void setupMetrics() { 127 | final Metrics metrics = metricsFactory.make(this, 17878); 128 | metrics.addCustomChart(new SimplePie("messengerType", () -> getSettings().getMessenger().name())); 129 | } 130 | 131 | @Override 132 | public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... exceptions) { 133 | if (exceptions.length > 0) { 134 | logger.info("Error: {}", message); 135 | logger.error(message, exceptions[0]); 136 | } else { 137 | if (level.equals(Level.SEVERE)) { 138 | logger.error(message); 139 | } else if (level.equals(Level.WARNING)) { 140 | logger.warn(message); 141 | } else { 142 | logger.info(message); 143 | } 144 | } 145 | } 146 | 147 | @Override 148 | public File getDataFolder() { 149 | return configDirectory.toFile(); 150 | } 151 | 152 | @Override 153 | @NotNull 154 | public ConcurrentMap> getRequests() { 155 | return requests; 156 | } 157 | 158 | @Override 159 | public String getServerType() { 160 | return "Velocity"; 161 | } 162 | 163 | @Override 164 | @NotNull 165 | public Collection getOnlineUsers() { 166 | return velocityUsers.values(); 167 | } 168 | 169 | @Override 170 | public Optional findPlayer(@NotNull UUID uuid) { 171 | return Optional.ofNullable(velocityUsers.get(uuid)); 172 | } 173 | 174 | public VelocityUser getPlayer(@NotNull Player player) { 175 | return velocityUsers.get(player.getUniqueId()); 176 | } 177 | 178 | @Override 179 | public void loadMessenger() { 180 | switch (settings.getMessenger()) { 181 | case REDIS -> messenger = new RedisMessenger(this, settings.getRedis(), false); 182 | case PLUGIN_MESSAGE -> messenger = new PluginMessageMessenger(this); 183 | } 184 | 185 | log(Level.INFO, "Loaded messenger " + getSettings().getMessenger().name()); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /velocity/src/main/java/net/william278/papiproxybridge/messenger/PluginMessageMessenger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.messenger; 21 | 22 | import com.velocitypowered.api.event.Subscribe; 23 | import com.velocitypowered.api.event.connection.PluginMessageEvent; 24 | import com.velocitypowered.api.proxy.Player; 25 | import com.velocitypowered.api.proxy.messages.ChannelIdentifier; 26 | import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; 27 | import lombok.RequiredArgsConstructor; 28 | import net.william278.papiproxybridge.PAPIProxyBridge; 29 | import net.william278.papiproxybridge.VelocityPAPIProxyBridge; 30 | import net.william278.papiproxybridge.user.VelocityUser; 31 | import org.jetbrains.annotations.NotNull; 32 | 33 | import java.util.Optional; 34 | import java.util.UUID; 35 | import java.util.logging.Level; 36 | 37 | @RequiredArgsConstructor 38 | public class PluginMessageMessenger extends Messenger { 39 | 40 | private final VelocityPAPIProxyBridge plugin; 41 | private ChannelIdentifier responseChannelIdentifier; 42 | private ChannelIdentifier responseComponentChannelIdentifier; 43 | 44 | @Override 45 | public void onEnable() { 46 | responseChannelIdentifier = new LegacyChannelIdentifier(PAPIProxyBridge.getChannel(false)); 47 | responseComponentChannelIdentifier = new LegacyChannelIdentifier(PAPIProxyBridge.getComponentChannel(false)); 48 | plugin.getServer().getChannelRegistrar().register(this.responseChannelIdentifier); 49 | plugin.getServer().getChannelRegistrar().register(this.responseComponentChannelIdentifier); 50 | plugin.getServer().getEventManager().register(plugin, this); 51 | } 52 | 53 | @Override 54 | public void sendMessage(@NotNull UUID uuid, @NotNull String channel, byte @NotNull [] message) { 55 | final Optional optionalVelocityUser = plugin.findPlayer(uuid); 56 | if (optionalVelocityUser.isEmpty()) { 57 | return; 58 | } 59 | 60 | final VelocityUser user = optionalVelocityUser.get(); 61 | final Player player = user.player(); 62 | player.getCurrentServer().ifPresent(server -> { 63 | if (!server.sendPluginMessage(new LegacyChannelIdentifier(channel), message)) { 64 | plugin.log(Level.SEVERE, "Failed to send plugin message to " + server.getServerInfo().getName() 65 | + " for player " + player.getUsername() + " on channel " 66 | + channel); 67 | } 68 | }); 69 | } 70 | 71 | @Subscribe 72 | public void onPluginMessageReceived(@NotNull PluginMessageEvent event) { 73 | final ChannelIdentifier channelId = event.getIdentifier(); 74 | if (!channelId.equals(this.responseChannelIdentifier) && !channelId.equals(this.responseComponentChannelIdentifier)) { 75 | return; 76 | } 77 | 78 | plugin.handleMessage(plugin, event.getIdentifier().getId(), event.getData(), false); 79 | event.setResult(PluginMessageEvent.ForwardResult.handled()); 80 | } 81 | 82 | @Override 83 | public void onDisable() { 84 | plugin.getServer().getChannelRegistrar().unregister(this.responseChannelIdentifier); 85 | plugin.getServer().getChannelRegistrar().unregister(this.responseComponentChannelIdentifier); 86 | plugin.getServer().getEventManager().unregisterListener(plugin, this); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /velocity/src/main/java/net/william278/papiproxybridge/user/VelocityUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of PAPIProxyBridge, licensed under the Apache License 2.0. 3 | * 4 | * Copyright (c) William278 5 | * Copyright (c) contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package net.william278.papiproxybridge.user; 21 | 22 | import com.velocitypowered.api.proxy.Player; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.util.UUID; 26 | 27 | public record VelocityUser(@NotNull Player player) implements ProxyUser { 28 | 29 | @NotNull 30 | public static VelocityUser adapt(@NotNull Player player) { 31 | return new VelocityUser(player); 32 | } 33 | 34 | @Override 35 | @NotNull 36 | public String getUsername() { 37 | return player.getUsername(); 38 | } 39 | 40 | @Override 41 | @NotNull 42 | public UUID getUniqueId() { 43 | return player.getUniqueId(); 44 | } 45 | 46 | @Override 47 | @NotNull 48 | public String getServerName() { 49 | return player.getCurrentServer() 50 | .map(serverConnection -> serverConnection.getServerInfo().getName()) 51 | .orElse("unknown"); 52 | } 53 | 54 | @Override 55 | public boolean isConnected() { 56 | return player.isActive(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /velocity/src/main/resources/velocity-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "papiproxybridge", 3 | "name": "PAPIProxyBridge", 4 | "version": "${version}", 5 | "description": "${description}", 6 | "url": "https://william278.net", 7 | "authors": [ 8 | "William278" 9 | ], 10 | "main": "net.william278.papiproxybridge.VelocityPAPIProxyBridge" 11 | } --------------------------------------------------------------------------------