2
0
mirror of https://github.com/proxiodev/RedisBungee.git synced 2026-05-03 11:40:29 +00:00

82 Commits

Author SHA1 Message Date
4221ebb892 Update readme to reflect the new project name 2026-03-18 21:16:24 +04:00
d93076eb81 maintenance: jitpack and github action move to java 21 2026-03-17 00:35:29 +04:00
1d2c11c538 maintenance: velocity require java 21 now for 3.5.0 2026-03-17 00:31:36 +04:00
e495b11587 maintenance: fix javadocs of bungeecord and update to 5 instead of 4 2026-03-17 00:28:08 +04:00
b250776c82 maintenance: remove java @Nullable parameter from Configuration class 2026-03-17 00:18:01 +04:00
13cfe8db13 maintenance: move to blossom v2 and use indragit to get git info 2026-03-17 00:17:12 +04:00
d30c70c8c4 maintenance: update versions
blossom update to 2v include indragit
2026-03-17 00:15:36 +04:00
3aa76384c3 maintenance: update gradle wrapper 2026-03-17 00:14:10 +04:00
92c965bfc6 readd leagacy methods to bungeecord only 2025-02-24 23:20:02 +04:00
1fb429ea77 finishup bungeecord, move to mini message as serialzier 2025-02-22 19:04:06 +04:00
338297192c reimpl kick api using serialized messages instead, Move language to own module, depenedcies fixes 2025-02-22 17:57:06 +04:00
1d3bd7e101 remove depercated apis | begin removing adventure api from the main api 2025-02-22 15:43:49 +04:00
96c0dff8c1 debug command introduction 2025-02-22 15:05:43 +04:00
Pierre-Olivier GOIN
23aeb81308 database address isn't the same in player manager (#127)
Maintain the same name in the Redis database as in the ProxyDataManager.

redis-bungee -> redisbungee
2025-02-22 11:08:40 +04:00
Pierre-Olivier GOIN
c84b987b9e Duplicated RB-Velocity in Gradle Settings (#128) 2025-02-22 11:05:49 +04:00
8177260991 make platforms can't see each other when using same redis server and network id 2025-02-21 19:44:52 +04:00
287f037774 Remove generics used for events in PlayerData 2025-01-28 20:14:45 +04:00
c633f1a106 properly cache last online and its invalidation 2025-01-28 20:14:45 +04:00
9fd1da5f92 fix depenedcies being copied 2025-01-28 20:14:45 +04:00
0050575aff update okhttp 2025-01-28 20:14:45 +04:00
6eab4ef602 move gradle plugins to libs file 2025-01-28 20:14:44 +04:00
12acc16376 build system improvement & update adventure 2025-01-28 20:14:44 +04:00
8b9af8838d Fix methods of Bungeecord listener being private
seems like i forgot to check the bungeecord impl correctly.
2025-01-28 20:14:44 +04:00
Dawid Sawicki
81bf06e2df Allow to customize ping events priority via handle-motd-order (#108)
Currently, the bungee implementation is using NORMAL priority for the
ping event handler, while the velocity implementation is using LAST.
Regardless of which choice may be the better one, this is an
inconsistency that this patch addresses by using NORMAL as the default
for both platforms.

Additionally to addressing the inconsistency, this patch adds a new
config option `handle-motd-order` which uses velocity's event priority
nomenclature to allow configuring the behavior of the MOTD handling on
both platforms.

In cases where there is a MOTD plugin that incorrectly overrides a
player count using the local player count, one may choose to use order
LAST to override the value back to the global player count.

In cases where there is a MOTD plugin that relies on a player count
value from the ping response, one may choose to use order FIRST to make
sure the response will have the correct global player count.

Fixes https://github.com/ProxioDev/ValioBungee/issues/107
2025-01-28 20:14:44 +04:00
41b1dab8cc update velocity runner 2025-01-28 20:14:44 +04:00
a437db32b8 update gradle, update depends 2025-01-28 20:14:44 +04:00
2bd79f628e runWaterfall & Waterfall proxy EOL 2025-01-28 20:14:44 +04:00
93c1cd8e4c 0.13.0-SNAPSHOT 2025-01-28 20:14:44 +04:00
f27d54beb8 0.12.6 2025-01-28 12:09:06 +04:00
91ea0b08dc relocate adventure api in bungeecord due collison with other plugins 2025-01-28 12:08:10 +04:00
199c1c7135 0.12.5 2024-09-28 12:34:23 +04:00
dab5f26e2c fix event double firing in velocity 2024-09-26 19:24:38 +04:00
c622bc7b63 move destroyProxyMembers to correct place
fixes null network id
2024-09-26 19:10:32 +04:00
881691a92d gradle wrapper upgrade, update bungeecord 2024-09-26 19:09:51 +04:00
079606c9da include new branches name in github file 2024-09-26 16:08:43 +04:00
ea54a0bc49 Update gradle.yml 2024-09-26 16:05:17 +04:00
69e91c3e42 check if player is really on the proxy when connecting
this prevents logged in error if somehow proxy shutdowns at weird time
2024-09-26 13:43:33 +04:00
Joël | NoPermission
d8704c8a8f Fixed null when fetching server id. (#118)
(cherry picked from commit 219a4ab360)
2024-09-26 12:18:46 +04:00
Efe Kurban
981d42d4a8 Added null-check for server keys (#106)
Fixes https://github.com/ProxioDev/ValioBungee/issues/105

(cherry picked from commit be0c6be2aa)
2024-09-26 12:18:24 +04:00
e0bca62cdb reintroduce 1 hour cache 2024-05-18 15:02:14 +04:00
md5nake
9ebfafbeef Invalidate serversToPlayersCache on player updates (#103)
This closes #102
2024-05-18 14:59:24 +04:00
70eebdc9ec Revert "reintroduce 1 hour cache, remove servers to player cache due changes can happen fast."
This reverts commit e85e18dad8.
2024-05-18 14:58:02 +04:00
e85e18dad8 reintroduce 1 hour cache, remove servers to player cache due changes can happen fast. 2024-05-18 14:46:23 +04:00
995c9045df 0.12.4 | fix send command to proxies not executing on sender proxy 2024-05-16 02:57:49 +04:00
2485150ddc 0.12.3 2024-05-15 22:08:18 +04:00
32735466d6 Deprecate old apis for removal in 0.13.0 2024-05-14 20:52:45 +04:00
e8715e5399 ignore IllegalStateException thrown by ServerConnection class in velocity 2024-05-14 19:23:09 +04:00
71287055b4 fix javadocs x2 2024-05-13 02:43:47 +04:00
8b48736bc1 fix javadocs 2024-05-13 02:38:58 +04:00
a2e6aff4c2 remove java docs from proxy impl 2024-05-13 02:33:56 +04:00
7c1c1183cf update gitignore 2024-05-13 02:27:44 +04:00
7f35b64d93 stupid git 2024-05-13 02:00:31 +04:00
3dc3d80045 update github workflow 2024-05-13 01:56:38 +04:00
765e6fe122 redo module system 2024-05-13 01:54:50 +04:00
e8514b3e8b 0.12.3 snapshot 2024-05-13 01:02:29 +04:00
e1d401639e even more oops: why caches are set to 1 hour ._. 2024-05-07 20:42:33 +04:00
f8c304d441 oops: remove cache of last-online
i must forgotten to remove it during development
2024-05-07 20:37:16 +04:00
e6b789229c bump version 0.12.2 2024-05-06 21:30:55 +04:00
025b555457 maven publish commands aswell 2024-05-06 21:27:37 +04:00
7029552c02 clarify compatibility mode error 2024-05-06 15:24:10 +04:00
62007992a7 bump 0.12.2-snapshot 2024-05-06 15:20:34 +04:00
8a6d97e923 bump to 0.12.1 2024-04-28 23:33:07 +04:00
a65b1cdf5c fix: adventure api version was replaced by guava 2024-04-28 23:32:08 +04:00
25441a5122 update .github/workflow/gradle.yml branch name 2024-04-28 23:18:01 +04:00
b9c7c31c09 oops: forgot to cache uuids/name of joined player 2024-04-28 23:16:19 +04:00
27358d2f5f change big uuid cache message to include the '/rb clean' 2024-04-28 23:16:18 +04:00
c736f39e7f bump version to 0.12.1-snapshot 2024-04-28 23:16:18 +04:00
7b90a34fae Update README.md 2024-04-28 20:36:09 +04:00
1593c2d628 0.12.0 (#86)
## NOTES
data system shouldn't effect anybody, unless you do any direct query to
Redis query data, you should adapt the changes, by viewing classes
`ProxyDataManager` and `PlayerDataManager`

# Changes 
* RedisBungee is compiled with `java 17` now, Due java 11 support is
ending at end of September
* config version is now `2` which will reset your config if older
version
* Adventure API is included inside RedisBungee API
* new Language infrastructure for RedisBungee built-in messages #85
*commands not included yet*
* New data system which replaces Redis PubSub with Redis Streams *see
below*
* Ability to connect player to last server they where on using an config
option #84
* new environment variable `REDISBUNGEE_PROXY_ID` which can be set
before launch
* new environment variable `REDISBUNGEE_NETWORK_ID` which can be set
before launch
* RedisBungee requires redis version 6.2 or above  #88 
* Better command system
https://github.com/ProxioDev/RedisBungee/issues/93

## New data system
Due limitation of Redis PubSub in Cluster environment, Internals of
RedisBungee were changed to support Redis Streams
- Network Ids
  - networks ids used to group network proxies
    - example having 'test' network and 'main' network
- Networks in the same redis server / cluster share the same UUID cache
- Heartbeat system:
- RedisBungee old heartbeat system used hastset on redisbungee to store
the current unix time of the proxy to check what every proxy died or
not, now instead we publish the heartbeat using unix time, and online
count to proxy which proxy store it in their memory, which allow the
`get number of online players` to be faster than pooling whole list in
old data system.

- PubSub
- since redisbungee was initially designed with pubsub in mind,
registration no longer required now for event to fire, see the api
changes below.

## Commands System
* rewritten using [acf lib](https://github.com/aikar/commands) to be
platform independent
* new command `/rb` or `/redisbungee` with sub commands `help`, `info`,
'clean', 'show'.

* 'rb'
  * '/rb' and '/rb info'

![image](https://github.com/ProxioDev/RedisBungee/assets/34905970/70796ab0-b5fd-4578-8c93-c976e517df95)

  * '/rb show'
  

![image](https://github.com/ProxioDev/RedisBungee/assets/34905970/56332c37-701f-43e0-946b-6894b845fab3)

* configuration to disable or override each command from legacy to new
introduced one `/rb`
```yaml

# For redis bungee legacy commands
# either can be run using '/rbl glist' for example
# or if 'install' is set to true '/glist' can be used.
# 'install' also overrides the proxy installed commands
#
# In legacy commands each command got it own permissions since they had it own permission pre new command system,
# so it's also applied to subcommands in '/rbl'.
commands:
  # Permission redisbungee.legacy.use
  redisbungee-legacy:
    enabled: false
    subcommands:
        # Permission redisbungee.command.glist
        glist:
          enabled: false
          install: false
        # Permission redisbungee.command.find
        find:
          enabled: false
          install: false
        # Permission redisbungee.command.lastseen
        lastseen:
          enabled: false
          install: false
        # Permission redisbungee.command.ip
        ip:
          enabled: false
          install: false
        # Permission redisbungee.command.pproxy
        pproxy:
          enabled: false
          install: false
        # Permission redisbungee.command.sendtoall
        sendtoall:
          enabled: false
          install: false
        # Permission redisbungee.command.serverid
        serverid:
          enabled: false
          install: false
        # Permission redisbungee.command.serverids
        serverids:
          enabled: false
          install: false
       # Permission redisbungee.command.plist
        plist:
          enabled: false
          install: false
  # Permission redisbungee.command.use
  redisbungee:
    enabled: true
```

## API changes
- Kick api Deprecated: 
  - `kickPlayer(String playerName, String message) `
  - `kickPlayer(UUID playerUUID, String message) `

- newer where added using adventure api:
  - `kickPlayer(String playerName, Component message) `
  - `kickPlayer(UUID playerUUID, Component message) `

-  PubSub registration api Deprecated:
```java
/**
     * Register (a) PubSub channel(s), so that you may handle PubSubMessageEvent for it.
     *
     * @param channels the channels to register
     * @since 0.3
     * @deprecated No longer required
     */
    @Deprecated
    public final void registerPubSubChannels(String... channels) {
    }

    /**
     * Unregister (a) PubSub channel(s).
     *
     * @param channels the channels to unregister
     * @since 0.3
     * @deprecated No longer required
     */
    @Deprecated
    public final void unregisterPubSubChannels(String... channels) {
    }

```
# Contributors

* `summoncraft.us` for running this branch in production
* @SrBedrock for providing [Brazilian
Portuguese](https://en.wikipedia.org/wiki/Brazilian_Portuguese)
translation #87
 
# issues
closes #84 
closes #88 
closes #92 
closes #81
closes #93

---------

Signed-off-by: mohammed jasem alaajel <xrambad@gmail.com>
Co-authored-by: ThiagoROX <51332006+SrBedrock@users.noreply.github.com>
2024-04-28 15:29:53 +04:00
5c4de82714 jitpack java 17] 2024-04-12 22:28:25 +04:00
9e48e472a6 0.11.4: maintenance update 2024-04-12 22:08:44 +04:00
b04e13136b github worker java 17 2024-04-12 22:05:30 +04:00
8821d3995c rename github workflow 2024-04-12 21:49:54 +04:00
9218e6ad42 update jedis 2024-04-12 21:42:32 +04:00
fa6bcf7cb8 update to 3.3.0-SNAPSHOT velocity 2024-04-12 19:34:20 +04:00
830b930394 credit source of ISSUE_TEMPLATE 2024-04-12 18:56:20 +04:00
450b0ee396 new issue template 2024-04-12 18:55:51 +04:00
dd4cc2a5bb remove matrix support 2023-12-27 02:07:33 +04:00
fd3aa51288 bump version 0.11.3 (#83) 2023-07-18 04:53:10 +04:00
Daniël Voort
99941c733f Use PlayerDB.co for name lookups (#82)
I noticed that currently name lookups for UUIDs that are not cached, are
not supported in RedisBungee due to Mojang removing name history.

When looking at the usages, RedisBungee internally only uses the current
name (So no need for full name history), so I moved name lookups to
[PlayerDB](https://playerdb.co/), which does not have API rate limits
unlike Mojang's API.

closes #59
2023-07-18 04:48:20 +04:00
7fb9c4689e 0.11.2 (#77)
- update gradle wrapper

- include a copy of RedisBungee LICENSE inside API resources folder.

- Fix depends issues on Older versions of Bungeecord proxy *travertine*

- Bump version 0.11.2
2023-06-03 15:30:48 +04:00
5066a18dd7 uncomment disable-kick-when-online in the config and make it false by default (#75)
closes #74
2023-05-02 20:14:58 +04:00
135 changed files with 4924 additions and 3879 deletions

52
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
# used https://github.com/PaperMC/Paper/blob/master/.github/ISSUE_TEMPLATE/behavior-bug-or-plugin-incompatibility.yml as template
name: Bug or incompatibility
description: report issues within RedisBungee
labels: [ "waiting" ]
body:
- type: textarea
attributes:
label: intended behavior
description: what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: what the behavior you actually saw
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: either a video, or way you want to present it.
validations:
required: true
- type: textarea
attributes:
label: Velocity or Bungeecord Versions
description: |
Please provide Proxy version for either Bungeecord or Velocity
validations:
required: true
- type: textarea
attributes:
label: RedisBungee Version & Redis Version
description: |
RedisBungee version is Different from Redis Version!
good example:
Redis Version: 7.2
RedisBungee Version: 0.12.0
in-case you used development branch you can use git commit hash Instead of the version
validations:
required: true
- type: textarea
attributes:
label: Other
description: |
extra information that we might need to know?
validations:
required: false

View File

@@ -1,32 +0,0 @@
---
name: Bug report
about: if you find a bug please report it here...
title: ''
labels: waiting
assignees: ham1255
---
**Describe the bug**
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Redis version? it should be at least 6 and above.**
**Bungeecord version or (the bungee fork name eg: waterfall) and your plugins**
**console logs?**
please provide any errors if there any.

View File

@@ -1,10 +0,0 @@
---
name: Question
about: ask your questions here
title: ''
labels: question
assignees: GovindasOM, ham1255
---
## What is your question?

10
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
# used https://github.com/PaperMC/Paper/blob/master/.github/ISSUE_TEMPLATE/behavior-bug-or-plugin-incompatibility.yml as template
name: Ask Question
description: Ask any questions to the developers
labels: [ "question"]
body:
- type: textarea
attributes:
label: Your question?
validations:
required: true

View File

@@ -5,9 +5,9 @@ name: RedisBungee Build
on:
push:
branches: [ develop ]
branches: [ stable, develop ]
pull_request:
branches: [ develop ]
branches: [ stable, develop ]
jobs:
build:
@@ -16,27 +16,27 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
- name: Set up JDK 21
uses: actions/setup-java@v2
with:
java-version: '11'
java-version: '21'
distribution: 'adopt'
- name: Build with gradle
run: ./gradlew shadowJar
- name: Upload Bungee
uses: actions/upload-artifact@v2.2.3
uses: actions/upload-artifact@v4.4.0
with:
# Artifact name
name: RedisBungee-Bungee
# Destination path
path: RedisBungee-Bungee/build/libs/*
path: proxies/bungeecord/build/libs/*
- name: Upload Velocity
uses: actions/upload-artifact@v2.2.3
uses: actions/upload-artifact@v4.4.0
with:
name: RedisBungee-Velocity
path: RedisBungee-Velocity/build/libs/*
path: proxies/velocity/build/libs/*
- name: Upload API
uses: actions/upload-artifact@v2.2.3
uses: actions/upload-artifact@v4.4.0
with:
name: RedisBungee-API
path: RedisBungee-API/build/libs/*
path: api/build/libs/*

2
.gitignore vendored
View File

@@ -46,4 +46,4 @@ manifest.mf
javadoc
# run-server folders
*/run
proxies/*/run

191
README.md
View File

@@ -1,184 +1,24 @@
# RedisBungee fork By Limework
*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do it, tell mojang to implement transfer packet*.
[Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/)
# ValioBungee: The RedisBungee Limework's Fork
The original project of RedisBungee is no longer maintained, so we have forked the plugin.
RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/)
to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) or [Velocity*](https://github.com/PaperMC/Velocity) proxies
Velocity*: *version 3.1.2 or above is only supported, any version below that might work but might be unstable* [#40](https://github.com/ProxioDev/RedisBungee/pull/40)
to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord)
or [Velocity*](https://github.com/PaperMC/Velocity) proxies
## Why different name?
Because from our current understanding we cant use Redis name due trademark restrictions.
so we settled with valiobungee. But it doesnt not effect any internals naming schemes like plugin id and so on.
## Downloads
[![](https://raw.githubusercontent.com/Prospector/badges/master/modrinth-badge-72h-padded.png)](https://modrinth.com/plugin/redisbungee)
or from github releases
## Wiki
https://github.com/ProxioDev/RedisBungee/releases
## notes
If you are looking to use Original RedisBungee without a change to internals,
with critical bugs fixed, please use version [0.6.5](https://github.com/ProxioDev/RedisBungee/releases/tag/0.6.5) and java docs For legacy Version [0.6.5](https://proxiodev.github.io/RedisBungee-JavaDocs/0.6.5-SNAPSHOT/)
as its last version before internal changes. please note that you will not get support for any old builds unless critical bugs effecting both 0.6.5 and 0.7.0 or above.
SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.87700/)
## Supported Redis versions
| Redis version | Supported |
|:-------------:|:---------:|
| 1.x.x | &#x2716; |
| 2.x.x | &#x2716; |
| 3.x.x | &#x2714; |
| 4.x.x | &#x2714; |
| 5.x.x | &#x2714; |
| 6.x.x | &#x2714; |
| 7.x.x | &#x2714; |
## Implementing RedisBungee in your plugin: [![RedisBungee Build](https://github.com/proxiodev/RedisBungee/actions/workflows/maven.yml/badge.svg)](https://github.com/Limework/RedisBungee/actions/workflows/maven.yml) [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee)
RedisBungee is distributed as a [Gradle](https://gradle.org/) project.
By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee)
# Setup jitpack repository
## maven
```xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
```
## gradle (kotlin dsl)
```kotlin
repositories {
maven("https://jitpack.io/")
}
```
# [BungeeCord](https://github.com/SpigotMC/BungeeCord)
add this in your project dependencies
## maven
```xml
<dependency>
<groupId>com.github.proxiodev.redisbungee</groupId>
<artifactId>RedisBungee-Bungee</artifactId>
<version>VERSION</version>
<scope>provided</scope>
<!-- <classifier>all</classifier> USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOCATION -->
</dependency>
```
## gradle (kotlin dsl)
```
implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0")
// USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOACTION AND REMOVE THE ABOVE STATEMENT
implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0:all") {
exclude("redis.clients", "jedis")
}
```
then in your project plugin.yml add `RedisBungee` to `depends` like this
```yaml
name: "yourplugin"
main: your.main.class
version: 1.0.0-SNAPSHOT
author: idk
depends: [ RedisBungee ]
```
## [Velocity](https://github.com/PaperMC/Velocity)
## maven
```xml
<dependency>
<groupId>com.github.proxiodev.redisbungee</groupId>
<artifactId>RedisBungee-Velocity</artifactId>
<version>VERSION</version>
<scope>provided</scope>
<!-- <classifier>all</classifier> USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOCATION -->
</dependency>
```
## gradle (kotlin dsl)
```
implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0")
// USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOACTION AND REMOVE THE ABOVE STATEMENT
implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0:all") {
exclude("redis.clients", "jedis")
}
```
then to make your plugin depends on RedisBungee, make sure your plugin class Annotation have `@Dependency(id = "redisbungee")` like this
```java
@Plugin(
id = "myplugin",
name = "My Plugin",
version = "0.1.0-beta",
dependencies = {
@Dependency(id = "redisbungee")
}
)
public class PluginMainClass {
}
```
## Getting the latest commits to your code
If you want to use the latest commits without waiting for releases.
first, install it to your maven local repo
```bash
git clone https://github.com/ProxioDev/RedisBungee.git
cd RedisBungee
./gradlew publishToMavenLocal
```
then use any of these in your project.
```xml
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee-Bungee</artifactId>
<version>VERSION</version>
<scope>provided</scope>
<!-- <classifier>all</classifier> USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOCATION -->
</dependency>
```
```xml
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee-Velocity</artifactId>
<version>VERSION</version>
<scope>provided</scope>
<!-- <classifier>all</classifier> USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOCATION -->
</dependency>
```
## Javadocs
* API: https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc/
* Velocity: https://ci.limework.net/RedisBungee/RedisBungee-Velocity/build/docs/javadoc/
* Bungeecord: https://ci.limework.net/RedisBungee/RedisBungee-Bungee/build/docs/javadoc/
## Configuration
**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The default [config](https://github.com/ProxioDev/RedisBungee/blob/develop/RedisBungee-API/src/main/resources/config.yml) is saved when the plugin first starts.
## compatibility with original RedisBungee in Bungeecord ecosystem
This fork ensures compatibility with old plugins, so it should work as drop replacement,
but since Api has been split from the platform there some changes that have to be done, so your plugin might not work if:
* there is none at the moment, please report any findings at the issue page.
Cluster mode compatibility in version 0.8.0:
If you are using static legacy method `RedisBungee#getPool()` it might fail in:
* if Cluster mode is enabled, due fact its Uses different classes
* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than Jedis
https://github.com/ProxioDev/ValioBungee/wiki
## Support
You can join our matrix room [here](https://matrix.to/#/!zhedzmRNSZXfuOPZUB:govindas.net?via=govindas.net&via=matrix.org)
![icon](https://matrix.org/images/matrix-logo-white.svg)
open an issue with question button
## License
@@ -186,12 +26,7 @@ This project is distributed under Eclipse Public License 1.0
You can find it [here](https://github.com/proxiodev/RedisBungee/blob/master/LICENSE)
You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be found [here](https://github.com/minecrafter/RedisBungee) or spigot page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/)
You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be
found [here](https://github.com/minecrafter/RedisBungee) or spigot
page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/)
## YourKit
YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/) and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/).
![YourKit](https://www.yourkit.com/images/yklogo.png)

View File

@@ -1,310 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.net.InetAddresses;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* This class manages all the data that RedisBungee fetches from Redis, along with updates to that data.
*
* @since 0.3.3
*/
public abstract class AbstractDataManager<P, PL, PD, PS> {
protected final RedisBungeePlugin<P> plugin;
private final Cache<UUID, String> serverCache = createCache();
private final Cache<UUID, String> proxyCache = createCache();
private final Cache<UUID, InetAddress> ipCache = createCache();
private final Cache<UUID, Long> lastOnlineCache = createCache();
private final Gson gson = new Gson();
public AbstractDataManager(RedisBungeePlugin<P> plugin) {
this.plugin = plugin;
}
private static <K, V> Cache<K, V> createCache() {
// TODO: Allow customization via cache specification, ala ServerListPlus
return CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}
public String getServer(final UUID uuid) {
P player = plugin.getPlayer(uuid);
if (player != null)
return plugin.isPlayerOnAServer(player) ? plugin.getPlayerServerName(player) : null;
try {
return serverCache.get(uuid, new RedisTask<String>(plugin) {
@Override
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "server"), "user not found");
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.logFatal("Unable to get server");
throw new RuntimeException("Unable to get server for " + uuid, e);
}
}
public String getProxy(final UUID uuid) {
P player = plugin.getPlayer(uuid);
if (player != null)
return plugin.getConfiguration().getProxyId();
try {
return proxyCache.get(uuid, new RedisTask<String>(plugin) {
@Override
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "proxy"), "user not found");
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.logFatal("Unable to get proxy");
throw new RuntimeException("Unable to get proxy for " + uuid, e);
}
}
public InetAddress getIp(final UUID uuid) {
P player = plugin.getPlayer(uuid);
if (player != null)
return plugin.getPlayerIp(player);
try {
return ipCache.get(uuid, new RedisTask<InetAddress>(plugin) {
@Override
public InetAddress unifiedJedisTask(UnifiedJedis unifiedJedis) {
String result = unifiedJedis.hget("player:" + uuid, "ip");
if (result == null)
throw new NullPointerException("user not found");
return InetAddresses.forString(result);
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.logFatal("Unable to get IP");
throw new RuntimeException("Unable to get IP for " + uuid, e);
}
}
public long getLastOnline(final UUID uuid) {
P player = plugin.getPlayer(uuid);
if (player != null)
return 0;
try {
return lastOnlineCache.get(uuid, new RedisTask<Long>(plugin) {
@Override
public Long unifiedJedisTask(UnifiedJedis unifiedJedis) {
String result = unifiedJedis.hget("player:" + uuid, "online");
return result == null ? -1 : Long.parseLong(result);
}
});
} catch (ExecutionException e) {
plugin.logFatal("Unable to get last time online");
throw new RuntimeException("Unable to get last time online for " + uuid, e);
}
}
protected void invalidate(UUID uuid) {
ipCache.invalidate(uuid);
lastOnlineCache.invalidate(uuid);
serverCache.invalidate(uuid);
proxyCache.invalidate(uuid);
}
// Invalidate all entries related to this player, since they now lie. (call invalidate(uuid))
public abstract void onPostLogin(PL event);
// Invalidate all entries related to this player, since they now lie. (call invalidate(uuid))
public abstract void onPlayerDisconnect(PD event);
public abstract void onPubSubMessage(PS event);
public abstract boolean handleKick(UUID target, String message);
protected void handlePubSubMessage(String channel, String message) {
if (!channel.equals("redisbungee-data"))
return;
// Partially deserialize the message so we can look at the action
JsonObject jsonObject = JsonParser.parseString(message).getAsJsonObject();
final String source = jsonObject.get("source").getAsString();
if (source.equals(plugin.getConfiguration().getProxyId()))
return;
DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString());
switch (action) {
case JOIN:
final DataManagerMessage<LoginPayload> message1 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<LoginPayload>>() {
}.getType());
proxyCache.put(message1.getTarget(), message1.getSource());
lastOnlineCache.put(message1.getTarget(), (long) 0);
ipCache.put(message1.getTarget(), message1.getPayload().getAddress());
plugin.executeAsync(() -> {
Object event = plugin.createPlayerJoinedNetworkEvent(message1.getTarget());
plugin.fireEvent(event);
});
break;
case LEAVE:
final DataManagerMessage<LogoutPayload> message2 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<LogoutPayload>>() {
}.getType());
invalidate(message2.getTarget());
lastOnlineCache.put(message2.getTarget(), message2.getPayload().getTimestamp());
plugin.executeAsync(() -> {
Object event = plugin.createPlayerLeftNetworkEvent(message2.getTarget());
plugin.fireEvent(event);
});
break;
case SERVER_CHANGE:
final DataManagerMessage<ServerChangePayload> message3 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<ServerChangePayload>>() {
}.getType());
serverCache.put(message3.getTarget(), message3.getPayload().getServer());
plugin.executeAsync(() -> {
Object event = plugin.createPlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer());
plugin.fireEvent(event);
});
break;
case KICK:
final DataManagerMessage<KickPayload> kickPayload = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<KickPayload>>() {
}.getType());
plugin.executeAsync(() -> handleKick(kickPayload.target, kickPayload.payload.message));
break;
}
}
public static class DataManagerMessage<T extends Payload> {
private final UUID target;
private final String source;
private final Action action; // for future use!
private final T payload;
public DataManagerMessage(UUID target, String source, Action action, T payload) {
this.target = target;
this.source = source;
this.action = action;
this.payload = payload;
}
public UUID getTarget() {
return target;
}
public String getSource() {
return source;
}
public Action getAction() {
return action;
}
public T getPayload() {
return payload;
}
public enum Action {
JOIN,
LEAVE,
KICK,
SERVER_CHANGE
}
}
public static abstract class Payload {
}
public static class KickPayload extends Payload {
private final String message;
public KickPayload(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public static class LoginPayload extends Payload {
private final InetAddress address;
public LoginPayload(InetAddress address) {
this.address = address;
}
public InetAddress getAddress() {
return address;
}
}
public static class ServerChangePayload extends Payload {
private final String server;
private final String oldServer;
public ServerChangePayload(String server, String oldServer) {
this.server = server;
this.oldServer = oldServer;
}
public String getServer() {
return server;
}
public String getOldServer() {
return oldServer;
}
}
public static class LogoutPayload extends Payload {
private final long timestamp;
public LogoutPayload(long timestamp) {
this.timestamp = timestamp;
}
public long getTimestamp() {
return timestamp;
}
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.io.ByteArrayDataOutput;
import com.google.gson.Gson;
import java.net.InetAddress;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public abstract class AbstractRedisBungeeListener<LE, PLE, PD, SC, PP, PM, PS> {
protected final RedisBungeePlugin<?> plugin;
protected final List<InetAddress> exemptAddresses;
protected final Gson gson = new Gson();
public AbstractRedisBungeeListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
this.plugin = plugin;
this.exemptAddresses = exemptAddresses;
}
public void onLogin(LE event) {}
public abstract void onPostLogin(PLE event);
public abstract void onPlayerDisconnect(PD event);
public abstract void onServerChange(SC event);
public abstract void onPing(PP event);
public abstract void onPluginMessage(PM event);
public abstract void onPubSubMessage(PS event);
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import redis.clients.jedis.JedisPubSub;
public class JedisPubSubHandler extends JedisPubSub {
private final RedisBungeePlugin<?> plugin;
public JedisPubSubHandler(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
@Override
public void onMessage(final String s, final String s2) {
if (s2.trim().length() == 0) return;
plugin.executeAsync(new Runnable() {
@Override
public void run() {
Object event = plugin.createPubSubEvent(s, s2);
plugin.fireEvent(event);
}
});
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class PubSubListener implements Runnable {
private JedisPubSubHandler jpsh;
private final Set<String> addedChannels = new HashSet<String>();
private final RedisBungeePlugin<?> plugin;
public PubSubListener(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
@Override
public void run() {
RedisTask<Void> subTask = new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
jpsh = new JedisPubSubHandler(plugin);
addedChannels.add("redisbungee-" + plugin.getConfiguration().getProxyId());
addedChannels.add("redisbungee-allservers");
addedChannels.add("redisbungee-data");
unifiedJedis.subscribe(jpsh, addedChannels.toArray(new String[0]));
return null;
}
};
try {
subTask.execute();
} catch (Exception e) {
plugin.logWarn("PubSub error, attempting to recover in 5 secs.");
plugin.executeAsyncAfter(this, TimeUnit.SECONDS, 5);
}
}
public void addChannel(String... channel) {
addedChannels.addAll(Arrays.asList(channel));
jpsh.subscribe(channel);
}
public void removeChannel(String... channel) {
Arrays.asList(channel).forEach(addedChannels::remove);
jpsh.unsubscribe(channel);
}
public void poison() {
addedChannels.clear();
jpsh.unsubscribe();
}
}

View File

@@ -1,275 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
/**
* This Class has all internal methods needed by every redis bungee plugin, and it can be used to implement another platforms than bungeecord or another forks of RedisBungee
* <p>
* Reason this is interface because some proxies implementations require the user to extend class for plugins for example bungeecord.
*
* @author Ham1255
* @since 0.7.0
*/
public interface RedisBungeePlugin<P> extends EventsPlatform {
default void initialize() {
}
default void stop() {
}
Summoner<?> getSummoner();
RedisBungeeConfiguration getConfiguration();
int getCount();
default int getCurrentCount() {
return new RedisTask<Long>(this) {
@Override
public Long unifiedJedisTask(UnifiedJedis unifiedJedis) {
long total = 0;
long redisTime = getRedisTime(unifiedJedis);
Map<String, String> heartBeats = unifiedJedis.hgetAll("heartbeats");
for (Map.Entry<String, String> stringStringEntry : heartBeats.entrySet()) {
String k = stringStringEntry.getKey();
String v = stringStringEntry.getValue();
long heartbeatTime = Long.parseLong(v);
if (heartbeatTime + RedisUtil.PROXY_TIMEOUT >= redisTime) {
total = total + unifiedJedis.scard("proxy:" + k + ":usersOnline");
}
}
return total;
}
}.execute().intValue();
}
Set<String> getLocalPlayersAsUuidStrings();
AbstractDataManager<P, ?, ?, ?> getDataManager();
default Set<UUID> getPlayers() {
return new RedisTask<Set<UUID>>(this) {
@Override
public Set<UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
try {
List<String> keys = new ArrayList<>();
for (String i : getProxiesIds()) {
keys.add("proxy:" + i + ":usersOnline");
}
if (!keys.isEmpty()) {
Set<String> users = unifiedJedis.sunion(keys.toArray(new String[0]));
if (users != null && !users.isEmpty()) {
for (String user : users) {
try {
setBuilder = setBuilder.add(UUID.fromString(user));
} catch (IllegalArgumentException ignored) {
}
}
}
}
} catch (JedisConnectionException e) {
// Redis server has disappeared!
logFatal("Unable to get connection from pool - did your Redis server go away?");
throw new RuntimeException("Unable to get all players online", e);
}
return setBuilder.build();
}
}.execute();
}
AbstractRedisBungeeAPI getAbstractRedisBungeeApi();
UUIDTranslator getUuidTranslator();
Multimap<String, UUID> serverToPlayersCache();
default Multimap<String, UUID> serversToPlayers() {
return new RedisTask<Multimap<String, UUID>>(this) {
@Override
public Multimap<String, UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
for (String serverId : getProxiesIds()) {
Set<String> players = unifiedJedis.smembers("proxy:" + serverId + ":usersOnline");
for (String player : players) {
String playerServer = unifiedJedis.hget("player:" + player, "server");
if (playerServer == null) {
continue;
}
builder.put(playerServer, UUID.fromString(player));
}
}
return builder.build();
}
}.execute();
}
default Set<UUID> getPlayersOnProxy(String proxyId) {
checkArgument(getProxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID");
return new RedisTask<Set<UUID>>(this) {
@Override
public Set<UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
Set<String> users = unifiedJedis.smembers("proxy:" + proxyId + ":usersOnline");
ImmutableSet.Builder<UUID> builder = ImmutableSet.builder();
for (String user : users) {
builder.add(UUID.fromString(user));
}
return builder.build();
}
}.execute();
}
default void sendProxyCommand(String proxyId, String command) {
checkArgument(getProxiesIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid");
sendChannelMessage("redisbungee-" + proxyId, command);
}
List<String> getProxiesIds();
default List<String> getCurrentProxiesIds(boolean lagged) {
return new RedisTask<List<String>>(this) {
@Override
public List<String> unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
long time = getRedisTime(unifiedJedis);
ImmutableList.Builder<String> servers = ImmutableList.builder();
Map<String, String> heartbeats = unifiedJedis.hgetAll("heartbeats");
for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
try {
long stamp = Long.parseLong(entry.getValue());
if (lagged ? time >= stamp + RedisUtil.PROXY_TIMEOUT : time <= stamp + RedisUtil.PROXY_TIMEOUT) {
servers.add(entry.getKey());
} else if (time > stamp + RedisUtil.PROXY_TIMEOUT) {
logWarn(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat.");
unifiedJedis.hdel("heartbeats", entry.getKey());
}
} catch (NumberFormatException ignored) {
}
}
return servers.build();
} catch (JedisConnectionException e) {
logFatal("Unable to fetch server IDs");
e.printStackTrace();
return Collections.singletonList(getConfiguration().getProxyId());
}
}
}.execute();
}
PubSubListener getPubSubListener();
default void sendChannelMessage(String channel, String message) {
new RedisTask<Void>(this) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
unifiedJedis.publish(channel, message);
} catch (JedisConnectionException e) {
// Redis server has disappeared!
logFatal("Unable to get connection from pool - did your Redis server go away?");
throw new RuntimeException("Unable to publish channel message", e);
}
return null;
}
}.execute();
}
void executeAsync(Runnable runnable);
void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time);
boolean isOnlineMode();
void logInfo(String msg);
void logWarn(String msg);
void logFatal(String msg);
P getPlayer(UUID uuid);
P getPlayer(String name);
UUID getPlayerUUID(String player);
String getPlayerName(UUID player);
String getPlayerServerName(P player);
boolean isPlayerOnAServer(P player);
InetAddress getPlayerIp(P player);
default void sendProxyCommand(String cmd) {
sendProxyCommand(getConfiguration().getProxyId(), cmd);
}
default Long getRedisTime(UnifiedJedis unifiedJedis) {
List<Object> data = (List<Object>) unifiedJedis.sendCommand(Protocol.Command.TIME);
List<String> times = new ArrayList<>();
data.forEach((o) -> times.add(new String((byte[])o)));
return getRedisTime(times);
}
default long getRedisTime(List<String> timeRes) {
return Long.parseLong(timeRes.get(0));
}
default void kickPlayer(UUID playerUniqueId, String message) {
// first handle on origin proxy if player not found publish the payload
if (!getDataManager().handleKick(playerUniqueId, message)) {
new RedisTask<Void>(this) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
PayloadUtils.kickPlayerPayload(playerUniqueId, message, unifiedJedis);
return null;
}
}.execute();
}
}
default void kickPlayer(String playerName, String message) {
// fetch the uuid from name
UUID playerUUID = getUuidTranslator().getTranslatedUuid(playerName, true);
kickPlayer(playerUUID, message);
}
RedisBungeeMode getRedisBungeeMode();
void updateProxiesIds();
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.config;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
public class RedisBungeeConfiguration {
public enum MessageType {
LOGGED_IN_OTHER_LOCATION,
ALREADY_LOGGED_IN
}
private final ImmutableMap<MessageType, String> messages;
public static final int CONFIG_VERSION = 1;
private final String proxyId;
private final List<InetAddress> exemptAddresses;
private final boolean registerLegacyCommands;
private final boolean overrideBungeeCommands;
private final boolean restoreOldKickBehavior;
public RedisBungeeConfiguration(String proxyId, List<String> exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, ImmutableMap<MessageType, String> messages, boolean restoreOldKickBehavior) {
this.proxyId = proxyId;
this.messages = messages;
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
for (String s : exemptAddresses) {
addressBuilder.add(InetAddresses.forString(s));
}
this.exemptAddresses = addressBuilder.build();
this.registerLegacyCommands = registerLegacyCommands;
this.overrideBungeeCommands = overrideBungeeCommands;
this.restoreOldKickBehavior = restoreOldKickBehavior;
}
public String getProxyId() {
return proxyId;
}
public List<InetAddress> getExemptAddresses() {
return exemptAddresses;
}
public boolean doRegisterLegacyCommands() {
return registerLegacyCommands;
}
public boolean doOverrideBungeeCommands() {
return overrideBungeeCommands;
}
public ImmutableMap<MessageType, String> getMessages() {
return messages;
}
public boolean restoreOldKickBehavior() {
return restoreOldKickBehavior;
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class HeartbeatTask extends RedisTask<Void>{
public final static TimeUnit REPEAT_INTERVAL_TIME_UNIT = TimeUnit.SECONDS;
public final static int INTERVAL = 1;
private final AtomicInteger globalPlayerCount;
public HeartbeatTask(RedisBungeePlugin<?> plugin, AtomicInteger globalPlayerCount) {
super(plugin);
this.globalPlayerCount = globalPlayerCount;
}
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
long redisTime = plugin.getRedisTime(unifiedJedis);
unifiedJedis.hset("heartbeats", plugin.getConfiguration().getProxyId(), String.valueOf(redisTime));
} catch (JedisConnectionException e) {
// Redis server has disappeared!
plugin.logFatal("Unable to update heartbeat - did your Redis server go away?");
e.printStackTrace();
return null;
}
try {
plugin.updateProxiesIds();
globalPlayerCount.set(plugin.getCurrentCount());
} catch (Throwable e) {
plugin.logFatal("Unable to update data - did your Redis server go away?");
e.printStackTrace();
}
return null;
}
}

View File

@@ -1,86 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.UnifiedJedis;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class InitialUtils {
public static void checkRedisVersion(RedisBungeePlugin<?> plugin) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
// This is more portable than INFO <section>
String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO));
for (String s : info.split("\r\n")) {
if (s.startsWith("redis_version:")) {
String version = s.split(":")[1];
plugin.logInfo("Redis server version: " + version);
if (!RedisUtil.isRedisVersionRight(version)) {
plugin.logFatal("Your version of Redis (" + version + ") is not at least version 3.0 RedisBungee requires a newer version of Redis.");
throw new RuntimeException("Unsupported Redis version detected");
}
long uuidCacheSize = unifiedJedis.hlen("uuid-cache");
if (uuidCacheSize > 750000) {
plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains");
}
break;
}
}
return null;
}
}.execute();
}
public static void checkIfRecovering(RedisBungeePlugin<?> plugin, Path dataFolder) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
Path crashFile = dataFolder.resolve("restarted_from_crash.txt");
if (Files.exists(crashFile)) {
try {
Files.delete(crashFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
plugin.logInfo("crash file was deleted continuing RedisBungee startup ");
} else if (unifiedJedis.hexists("heartbeats", plugin.getConfiguration().getProxyId())) {
try {
long value = Long.parseLong(unifiedJedis.hget("heartbeats", plugin.getConfiguration().getProxyId()));
long redisTime = plugin.getRedisTime(unifiedJedis);
if (redisTime < value + RedisUtil.PROXY_TIMEOUT) {
logImposter(plugin);
throw new RuntimeException("Possible impostor instance!");
}
} catch (NumberFormatException ignored) {
}
}
return null;
}
}.execute();
}
private static void logImposter(RedisBungeePlugin<?> plugin) {
plugin.logFatal("You have launched a possible impostor Velocity / Bungeecord instance. Another instance is already running.");
plugin.logFatal("For data consistency reasons, RedisBungee will now disable itself.");
plugin.logFatal("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check.");
}
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import redis.clients.jedis.UnifiedJedis;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public abstract class IntegrityCheckTask extends RedisTask<Void> {
public static int INTERVAL = 30;
public static TimeUnit TIMEUNIT = TimeUnit.SECONDS;
public IntegrityCheckTask(RedisBungeePlugin<?> plugin) {
super(plugin);
}
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
Set<String> players = plugin.getLocalPlayersAsUuidStrings();
Set<String> playersInRedis = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline");
List<String> lagged = plugin.getCurrentProxiesIds(true);
// Clean up lagged players.
for (String s : lagged) {
Set<String> laggedPlayers = unifiedJedis.smembers("proxy:" + s + ":usersOnline");
unifiedJedis.del("proxy:" + s + ":usersOnline");
if (!laggedPlayers.isEmpty()) {
plugin.logInfo("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)...");
for (String laggedPlayer : laggedPlayers) {
PlayerUtils.cleanUpPlayer(laggedPlayer, unifiedJedis, true);
}
}
}
Set<String> absentLocally = new HashSet<>(playersInRedis);
absentLocally.removeAll(players);
Set<String> absentInRedis = new HashSet<>(players);
absentInRedis.removeAll(playersInRedis);
for (String member : absentLocally) {
boolean found = false;
for (String proxyId : plugin.getProxiesIds()) {
if (proxyId.equals(plugin.getConfiguration().getProxyId())) continue;
if (unifiedJedis.sismember("proxy:" + proxyId + ":usersOnline", member)) {
// Just clean up the set.
found = true;
break;
}
}
if (!found) {
PlayerUtils.cleanUpPlayer(member, unifiedJedis, false);
plugin.logWarn("Player found in set that was not found locally and globally: " + member);
} else {
unifiedJedis.srem("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline", member);
plugin.logWarn("Player found in set that was not found locally, but is on another proxy: " + member);
}
}
// due unifiedJedis does not support pipelined.
//Pipeline pipeline = jedis.pipelined();
for (String player : absentInRedis) {
// Player not online according to Redis but not BungeeCord.
handlePlatformPlayer(player, unifiedJedis);
}
} catch (Throwable e) {
plugin.logFatal("Unable to fix up stored player data");
e.printStackTrace();
}
return null;
}
public abstract void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis);
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.UnifiedJedis;
import java.util.Set;
public class ShutdownUtils {
public static void shutdownCleanup(RedisBungeePlugin<?> plugin) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
unifiedJedis.hdel("heartbeats", plugin.getConfiguration().getProxyId());
if (unifiedJedis.scard("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline") > 0) {
Set<String> players = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline");
for (String member : players)
PlayerUtils.cleanUpPlayer(member, unifiedJedis, true);
}
return null;
}
}.execute();
}
}

View File

@@ -1,20 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.api.util.io;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class IOUtil {
public static String readInputStreamAsString(InputStream is) {
String string;
try {
string = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new AssertionError(e);
}
return string;
}
}

View File

@@ -1,41 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.api.util.payload;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.UUID;
public class PayloadUtils {
private static final Gson gson = new Gson();
public static void playerJoinPayload(UUID uuid, UnifiedJedis unifiedJedis, InetAddress inetAddress) {
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.JOIN,
new AbstractDataManager.LoginPayload(inetAddress))));
}
public static void playerQuitPayload(String uuid, UnifiedJedis unifiedJedis, long timestamp) {
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
UUID.fromString(uuid), AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.LEAVE,
new AbstractDataManager.LogoutPayload(timestamp))));
}
public static void playerServerChangePayload(UUID uuid, UnifiedJedis unifiedJedis, String newServer, String oldServer) {
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.SERVER_CHANGE,
new AbstractDataManager.ServerChangePayload(newServer, oldServer))));
}
public static void kickPlayerPayload(UUID uuid, String message, UnifiedJedis unifiedJedis) {
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.KICK,
new AbstractDataManager.KickPayload(message))));
}
}

View File

@@ -1,57 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.api.util.player;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerJoinPayload;
import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerQuitPayload;
public class PlayerUtils {
public static void cleanUpPlayer(String uuid, UnifiedJedis rsc, boolean firePayload) {
final long timestamp = System.currentTimeMillis();
final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid, rsc);
rsc.srem("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid);
if (!isKickedFromOtherLocation) {
rsc.hdel("player:" + uuid, "server", "ip", "proxy");
rsc.hset("player:" + uuid, "online", String.valueOf(timestamp));
}
if (firePayload && !isKickedFromOtherLocation) {
playerQuitPayload(uuid, rsc, timestamp);
}
}
public static void setKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) {
// set anything for sake of exists check. then expire it after 2 seconds. should be great?
unifiedJedis.set("kicked-other-location::" + uuid, "0");
unifiedJedis.expire("kicked-other-location::" + uuid, 2);
}
public static boolean isKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) {
return unifiedJedis.exists("kicked-other-location::" + uuid);
}
public static void createPlayer(UUID uuid, UnifiedJedis unifiedJedis, String currentServer, InetAddress hostname, boolean fireEvent) {
final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid.toString(), unifiedJedis);
Map<String, String> playerData = new HashMap<>(4);
playerData.put("online", "0");
playerData.put("ip", hostname.getHostName());
playerData.put("proxy", AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId());
if (currentServer != null) {
playerData.put("server", currentServer);
}
unifiedJedis.sadd("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid.toString());
unifiedJedis.hset("player:" + uuid, playerData);
if (fireEvent && !isKickedFromOtherLocation) {
playerJoinPayload(uuid, unifiedJedis, hostname);
}
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.ResponseBody;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Deprecated
public class NameFetcher {
private static OkHttpClient httpClient;
private static final Gson gson = new Gson();
@Deprecated
public static void setHttpClient(OkHttpClient httpClient) {
throw new UnsupportedOperationException("Due mojang disabled the Names API NameFetcher no longer functions and has been disabled");
// NameFetcher.httpClient = httpClient;
}
@Deprecated
public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException {
throw new UnsupportedOperationException("Due mojang disabled the Names API NameFetcher no longer functions and has been disabled");
// String url = "https://api.mojang.com/user/profiles/" + uuid.toString().replace("-", "") + "/names";
// Request request = new Request.Builder().url(url).get().build();
// ResponseBody body = httpClient.newCall(request).execute().body();
// String response = body.string();
// body.close();
//
// Type listType = new TypeToken<List<Name>>() {
// }.getType();
// List<Name> names = gson.fromJson(response, listType);
//
// List<String> humanNames = new ArrayList<>();
// for (Name name : names) {
// humanNames.add(name.name);
// }
// return humanNames;
}
@Deprecated
public static class Name {
private String name;
private long changedToAt;
}
}

View File

@@ -1,82 +0,0 @@
# RedisBungee configuration file.
# Get Redis from http://redis.io/
# The Redis server you use.
# these settings are ignored when cluster mode is enabled.
redis-server: 127.0.0.1
redis-port: 6379
# Cluster Mode
# enabling this option will enable cluster mode.
cluster-mode-enabled: false
# FORMAT:
# redis-cluster-servers:
# - host: 127.0.0.1
# port: 2020
# - host: 127.0.0.1
# port: 2021
# - host: 127.0.0.1
# port: 2021
# you can set single server and Jedis will automatically discover cluster nodes,
# but might fail if this single redis node is down when Proxy startup, its recommended put the all the nodes
redis-cluster-servers:
- host: 127.0.0.1
port: 6379
# THIS FEATURE IS REDIS V6+
# OPTIONAL: if your redis uses acl usernames set the username here. leave empty for no username.
redis-username: ""
# OPTIONAL but recommended: If your Redis server uses AUTH, set the password required.
redis-password: ""
# Maximum connections that will be maintained to the Redis server.
# The default is 10. This setting should be left as-is unless you have some wildly
# inefficient plugins or a lot of players.
max-redis-connections: 10
# since redis can support ssl by version 6 you can use ssl / tls in redis bungee too!
# but there is more configuration needed to work see https://github.com/ProxioDev/RedisBungee/issues/18
# Keep note that SSL/TLS connections will decrease redis performance so use it when needed.
useSSL: false
# An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank.
proxy-id: "test-1"
# since version 0.8.0 Internally now uses JedisPooled instead of Jedis, JedisPool.
# which will break compatibility with old plugins that uses RedisBungee JedisPool
# so to mitigate this issue, we will instruct RedisBungee to init an JedisPool for compatibility reasons.
# enabled by default
# ignored when cluster mode is enabled
enable-jedis-pool-compatibility: true
# max connections for the compatibility pool
compatibility-max-connections: 3
# Register redis bungee legacy commands
# if this disabled override-bungee-commands will be ignored
register-legacy-commands: false
# Whether or not RedisBungee should install its version of regular BungeeCord commands.
# Often, the RedisBungee commands are desired, but in some cases someone may wish to
# override the commands using another plugin.
#
# If you are just denying access to the commands, RedisBungee uses the default BungeeCord
# permissions - just deny them and access will be denied.
#
# Please note that with build 787+, most commands overridden by RedisBungee were moved to
# modules, and these must be disabled or overridden yourself.
override-bungee-commands: false
# A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic
# restart scripts.
exempt-ip-addresses: []
# restore old login when online behavior before 0.9.0 update
# uncomment to enable it
# disable-kick-when-online: true
# Config version DO NOT CHANGE!!!!
config-version: 1

View File

@@ -1,2 +0,0 @@
logged-in-other-location: "§cYou logged in from another location!"
already-logged-in: "§cYou are already logged in!"

View File

@@ -1,82 +0,0 @@
plugins {
`java-library`
`maven-publish`
id("com.github.johnrengelman.shadow") version "8.1.1"
id("xyz.jpenilla.run-waterfall") version "2.0.0"
}
repositories {
mavenCentral()
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } // bungeecord
}
val bungeecordApiVersion = "1.19-R0.1-SNAPSHOT"
dependencies {
api(project(":RedisBungee-API")) {
exclude("com.google.guava", "guava")
exclude("com.google.code.gson", "gson")
}
compileOnly("net.md-5:bungeecord-api:$bungeecordApiVersion")
}
description = "RedisBungee Bungeecord implementation"
java {
withJavadocJar()
withSourcesJar()
}
tasks {
withType<Javadoc> {
dependsOn(project(":RedisBungee-API").getTasksByName("javadoc", false))
val options = options as StandardJavadocDocletOptions
options.use()
options.isDocFilesSubDirs = true
options.links(
"https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/", // bungeecord api
)
val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc")
options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path)
}
runWaterfall {
waterfallVersion("1.19")
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(8)
}
javadoc {
options.encoding = Charsets.UTF_8.name()
}
processResources {
filteringCharset = Charsets.UTF_8.name()
filesMatching("plugin.yml") {
filter {
it.replace("*{redisbungee.version}*", "$version", false)
}
}
}
shadowJar {
relocate("redis.clients.jedis", "com.imaginarycode.minecraft.redisbungee.internal.jedis")
relocate("redis.clients.util", "com.imaginarycode.minecraft.redisbungee.internal.jedisutil")
relocate("org.apache.commons.pool", "com.imaginarycode.minecraft.redisbungee.internal.commonspool")
relocate("com.squareup.okhttp", "com.imaginarycode.minecraft.redisbungee.internal.okhttp")
relocate("okio", "com.imaginarycode.minecraft.redisbungee.internal.okio")
relocate("org.json", "com.imaginarycode.minecraft.redisbungee.internal.json")
// configurate shade
relocate("ninja.leaping.configurate", "com.imaginarycode.minecraft.redisbungee.internal.configurate")
relocate("org.yaml", "com.imaginarycode.minecraft.redisbungee.internal.yml")
}
}
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import java.util.UUID;
public class BungeeDataManager extends AbstractDataManager<ProxiedPlayer, PostLoginEvent, PlayerDisconnectEvent, PubSubMessageEvent> implements Listener {
public BungeeDataManager(RedisBungeePlugin<ProxiedPlayer> plugin) {
super(plugin);
}
@Override
@EventHandler
public void onPostLogin(PostLoginEvent event) {
invalidate(event.getPlayer().getUniqueId());
}
@Override
@EventHandler
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
invalidate(event.getPlayer().getUniqueId());
}
@Override
@EventHandler
public void onPubSubMessage(PubSubMessageEvent event) {
handlePubSubMessage(event.getChannel(), event.getMessage());
}
@Override
public boolean handleKick(UUID target, String message) {
// check if the player is online on this proxy
ProxiedPlayer player = plugin.getPlayer(target);
if (player == null) return false;
player.disconnect(TextComponent.fromLegacyText(message));
return true;
}
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import redis.clients.jedis.UnifiedJedis;
public class BungeePlayerUtils {
public static void createBungeePlayer(ProxiedPlayer player, UnifiedJedis unifiedJedis, boolean fireEvent) {
String serverName = null;
if (player.getServer() != null) {
serverName = player.getServer().getInfo().getName();
}
PendingConnection pendingConnection = player.getPendingConnection();
PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, pendingConnection.getAddress().getAddress(), fireEvent);
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
/**
* This platform class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungeeAPI#getRedisBungeeApi()}
* or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}.
*
* @author tuxed
* @since 0.2.3 | updated 0.8.0
*/
public class RedisBungeeAPI extends AbstractRedisBungeeAPI {
private static RedisBungeeAPI redisBungeeApi;
public RedisBungeeAPI(RedisBungeePlugin<?> plugin) {
super(plugin);
if (redisBungeeApi == null) {
redisBungeeApi = this;
}
}
/**
* Get the server where the specified player is playing. This function also deals with the case of local players
* as well, and will return local information on them.
*
* @param player a player uuid
* @return {@link ServerInfo} Can be null if proxy can't find it.
* @see #getServerNameFor(UUID)
*/
@Nullable
public final ServerInfo getServerFor(@NonNull UUID player) {
String serverName = this.getServerNameFor(player);
if (serverName == null) return null;
return ((Plugin) this.plugin).getProxy().getServerInfo(serverName);
}
/**
* Api instance
*
* @return the API instance.
* @since 0.6.5
*/
public static RedisBungeeAPI getRedisBungeeApi() {
return redisBungeeApi;
}
}

View File

@@ -1,252 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.api.AbstractRedisBungeeListener;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import net.md_5.bungee.api.AbstractReconnectHandler;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.*;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.*;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultiset;
import static net.md_5.bungee.event.EventPriority.HIGHEST;
public class RedisBungeeBungeeListener extends AbstractRedisBungeeListener<LoginEvent, PostLoginEvent, PlayerDisconnectEvent, ServerConnectedEvent, ProxyPingEvent, PluginMessageEvent, PubSubMessageEvent> implements Listener {
public RedisBungeeBungeeListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
super(plugin, exemptAddresses);
}
@Override
@EventHandler(priority = HIGHEST)
public void onLogin(LoginEvent event) {
event.registerIntent((Plugin) plugin);
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
if (event.isCancelled()) {
return null;
}
if (plugin.getConfiguration().restoreOldKickBehavior()) {
for (String s : plugin.getProxiesIds()) {
if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) {
event.setCancelled(true);
event.setCancelReason(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN));
return null;
}
}
} else if (api.isPlayerOnline(event.getConnection().getUniqueId())) {
PlayerUtils.setKickedOtherLocation(event.getConnection().getUniqueId().toString(), unifiedJedis);
api.kickPlayer(event.getConnection().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION));
}
return null;
} finally {
event.completeIntent((Plugin) plugin);
}
}
});
}
@Override
@EventHandler
public void onPostLogin(PostLoginEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), unifiedJedis);
BungeePlayerUtils.createBungeePlayer(event.getPlayer(), unifiedJedis, true);
return null;
}
});
}
@Override
@EventHandler
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true);
return null;
}
});
}
@Override
@EventHandler
public void onServerChange(ServerConnectedEvent event) {
final String currentServer = event.getServer().getInfo().getName();
final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName();
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getInfo().getName());
PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer);
return null;
}
});
}
@Override
@EventHandler
public void onPing(ProxyPingEvent event) {
if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) {
return;
}
ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
if (forced != null && event.getConnection().getListener().isPingPassthrough()) {
return;
}
event.getResponse().getPlayers().setOnline(plugin.getCount());
}
@Override
@SuppressWarnings("UnstableApiUsage")
@EventHandler
public void onPluginMessage(PluginMessageEvent event) {
if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) {
final String currentChannel = event.getTag();
final byte[] data = Arrays.copyOf(event.getData(), event.getData().length);
plugin.executeAsync(() -> {
ByteArrayDataInput in = ByteStreams.newDataInput(data);
String subchannel = in.readUTF();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String type;
switch (subchannel) {
case "PlayerList":
out.writeUTF("PlayerList");
Set<UUID> original = Collections.emptySet();
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
original = plugin.getPlayers();
} else {
out.writeUTF(type);
try {
original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) {
}
}
Set<String> players = new HashSet<>();
for (UUID uuid : original)
players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false));
out.writeUTF(Joiner.on(',').join(players));
break;
case "PlayerCount":
out.writeUTF("PlayerCount");
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
out.writeInt(plugin.getCount());
} else {
out.writeUTF(type);
try {
out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size());
} catch (IllegalArgumentException e) {
out.writeInt(0);
}
}
break;
case "LastOnline":
String user = in.readUTF();
out.writeUTF("LastOnline");
out.writeUTF(user);
out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true))));
break;
case "ServerPlayers":
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
boolean includesUsers;
switch (type1) {
case "COUNT":
includesUsers = false;
break;
case "PLAYERS":
includesUsers = true;
break;
default:
// TODO: Should I raise an error?
return;
}
out.writeUTF(type1);
if (includesUsers) {
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : multimap.entries()) {
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
}
serializeMultimap(human, true, out);
} else {
serializeMultiset(multimap.keys(), out);
}
break;
case "Proxy":
out.writeUTF("Proxy");
out.writeUTF(plugin.getConfiguration().getProxyId());
break;
case "PlayerProxy":
String username = in.readUTF();
out.writeUTF("PlayerProxy");
out.writeUTF(username);
out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true))));
break;
default:
return;
}
((Server) event.getSender()).sendData(currentChannel, out.toByteArray());
});
}
}
@Override
@EventHandler
public void onPubSubMessage(PubSubMessageEvent event) {
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) {
String message = event.getMessage();
if (message.startsWith("/"))
message = message.substring(1);
plugin.logInfo("Invoking command via PubSub: /" + message);
((Plugin) plugin).getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), message);
}
}
}

View File

@@ -1,353 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.plugin.Command;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
/**
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
* <p>
* All classes use the {@link AbstractRedisBungeeAPI}.
*
* @author tuxed
* @since 0.2.3
*/
public class RedisBungeeCommands {
private static final BaseComponent[] NO_PLAYER_SPECIFIED =
new ComponentBuilder("You must specify a player name.").color(ChatColor.RED).create();
private static final BaseComponent[] PLAYER_NOT_FOUND =
new ComponentBuilder("No such player found.").color(ChatColor.RED).create();
private static final BaseComponent[] NO_COMMAND_SPECIFIED =
new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create();
private static String playerPlural(int num) {
return num == 1 ? num + " player is" : num + " players are";
}
public static class GlistCommand extends Command {
private final RedisBungee plugin;
public GlistCommand(RedisBungee plugin) {
super("glist", "bungeecord.command.list", "redisbungee", "rglist");
this.plugin = plugin;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
int count = plugin.getAbstractRedisBungeeApi().getPlayerCount();
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
.append(playerPlural(count) + " currently online.").create();
if (args.length > 0 && args[0].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
// if for any reason UUID translation fails just return the uuid as name, to make command finish executing.
String playerName = plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false);
human.put(entry.getKey(), playerName != null ? playerName : entry.getValue().toString());
}
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
TextComponent serverName = new TextComponent();
serverName.setColor(ChatColor.GREEN);
serverName.setText("[" + server + "] ");
TextComponent serverCount = new TextComponent();
serverCount.setColor(ChatColor.YELLOW);
serverCount.setText("(" + serverToPlayers.get(server).size() + "): ");
TextComponent serverPlayers = new TextComponent();
serverPlayers.setColor(ChatColor.WHITE);
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
sender.sendMessage(serverName, serverCount, serverPlayers);
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create());
}
}
});
}
}
public static class FindCommand extends Command {
private final RedisBungee plugin;
public FindCommand(RedisBungee plugin) {
super("find", "bungeecord.command.find", "rfind");
this.plugin = plugin;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
ServerInfo si = plugin.getProxy().getServerInfo(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid));
if (si != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.BLUE);
message.setText(args[0] + " is on " + si.getName() + ".");
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}
});
}
}
public static class LastSeenCommand extends Command {
private final RedisBungee plugin;
public LastSeenCommand(RedisBungee plugin) {
super("lastseen", "redisbungee.command.lastseen", "rlastseen");
this.plugin = plugin;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid);
TextComponent message = new TextComponent();
if (secs == 0) {
message.setColor(ChatColor.GREEN);
message.setText(args[0] + " is currently online.");
} else if (secs != -1) {
message.setColor(ChatColor.BLUE);
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
} else {
message.setColor(ChatColor.RED);
message.setText(args[0] + " has never been online.");
}
sender.sendMessage(message);
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}
});
}
}
public static class IpCommand extends Command {
private final RedisBungee plugin;
public IpCommand(RedisBungee plugin) {
super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
this.plugin = plugin;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid);
if (ia != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.GREEN);
message.setText(args[0] + " is connected from " + ia.toString() + ".");
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}
});
}
}
public static class PlayerProxyCommand extends Command {
private final RedisBungee plugin;
public PlayerProxyCommand(RedisBungee plugin) {
super("pproxy", "redisbungee.command.pproxy");
this.plugin = plugin;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid);
if (proxy != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.GREEN);
message.setText(args[0] + " is connected to " + proxy + ".");
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}
});
}
}
public static class SendToAll extends Command {
private final RedisBungee plugin;
public SendToAll(RedisBungee plugin) {
super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (args.length > 0) {
String command = Joiner.on(" ").skipNulls().join(args);
plugin.getAbstractRedisBungeeApi().sendProxyCommand(command);
TextComponent message = new TextComponent();
message.setColor(ChatColor.GREEN);
message.setText("Sent the command /" + command + " to all proxies.");
sender.sendMessage(message);
} else {
sender.sendMessage(NO_COMMAND_SPECIFIED);
}
}
}
public static class ServerId extends Command {
private final RedisBungee plugin;
public ServerId(RedisBungee plugin) {
super("serverid", "redisbungee.command.serverid", "rserverid");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
TextComponent textComponent = new TextComponent();
textComponent.setText("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".");
textComponent.setColor(ChatColor.YELLOW);
sender.sendMessage(textComponent);
}
}
public static class ServerIds extends Command {
private final RedisBungee plugin;
public ServerIds(RedisBungee plugin) {
super("serverids", "redisbungee.command.serverids");
this.plugin =plugin;
}
@Override
public void execute(CommandSender sender, String[] strings) {
TextComponent textComponent = new TextComponent();
textComponent.setText("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()));
textComponent.setColor(ChatColor.YELLOW);
sender.sendMessage(textComponent);
}
}
public static class PlistCommand extends Command {
private final RedisBungee plugin;
public PlistCommand(RedisBungee plugin) {
super("plist", "redisbungee.command.plist", "rplist");
this.plugin = plugin;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId();
if (!plugin.getProxiesIds().contains(proxy)) {
sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create());
return;
}
Set<UUID> players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy);
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
.append(playerPlural(players.size()) + " currently on proxy " + proxy + ".").create();
if (args.length >= 2 && args[1].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
if (players.contains(entry.getValue())) {
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
}
}
for (String server : new TreeSet<>(human.keySet())) {
TextComponent serverName = new TextComponent();
serverName.setColor(ChatColor.RED);
serverName.setText("[" + server + "] ");
TextComponent serverCount = new TextComponent();
serverCount.setColor(ChatColor.YELLOW);
serverCount.setText("(" + human.get(server).size() + "): ");
TextComponent serverPlayers = new TextComponent();
serverPlayers.setColor(ChatColor.WHITE);
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
sender.sendMessage(serverName, serverCount, serverPlayers);
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(new ComponentBuilder("To see all players online, use /plist " + proxy + " showall.").color(ChatColor.YELLOW).create());
}
}
});
}
}
}

View File

@@ -1,74 +0,0 @@
plugins {
`java-library`
`maven-publish`
id("com.github.johnrengelman.shadow") version "8.1.1"
id("xyz.jpenilla.run-velocity") version "2.0.0"
}
repositories {
mavenCentral()
maven { url = uri("https://repo.papermc.io/repository/maven-public/") }
}
dependencies {
api(project(":RedisBungee-API")) {
// Since velocity already includes guava / configurate exlude them
exclude("com.google.guava", "guava")
exclude("com.google.code.gson", "gson")
exclude("org.spongepowered", "configurate-yaml")
}
compileOnly("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT")
annotationProcessor("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT")
}
description = "RedisBungee Velocity implementation"
java {
withJavadocJar()
withSourcesJar()
}
tasks {
withType<Javadoc> {
dependsOn(project(":RedisBungee-API").getTasksByName("javadoc", false))
val options = options as StandardJavadocDocletOptions
options.use()
options.isDocFilesSubDirs = true
options.links(
"https://jd.papermc.io/velocity/3.0.0/", // velocity api
)
val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc")
options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path)
}
runVelocity {
velocityVersion("3.2.0-SNAPSHOT")
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(11)
}
javadoc {
options.encoding = Charsets.UTF_8.name()
}
processResources {
filteringCharset = Charsets.UTF_8.name()
}
shadowJar {
relocate("redis.clients.jedis", "com.imaginarycode.minecraft.redisbungee.internal.jedis")
relocate("redis.clients.util", "com.imaginarycode.minecraft.redisbungee.internal.jedisutil")
relocate("org.apache.commons.pool", "com.imaginarycode.minecraft.redisbungee.internal.commonspool")
relocate("com.squareup.okhttp", "com.imaginarycode.minecraft.redisbungee.internal.okhttp")
relocate("okio", "com.imaginarycode.minecraft.redisbungee.internal.okio")
relocate("org.json", "com.imaginarycode.minecraft.redisbungee.internal.json")
}
}
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
}

View File

@@ -1,263 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.api.AbstractRedisBungeeListener;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.velocitypowered.api.event.Continuation;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.*;
import java.util.stream.Collectors;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultiset;
public class RedisBungeeVelocityListener extends AbstractRedisBungeeListener<LoginEvent, PostLoginEvent, DisconnectEvent, ServerConnectedEvent, ProxyPingEvent, PluginMessageEvent, PubSubMessageEvent> {
// Some messages are using legacy characters
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection();
public RedisBungeeVelocityListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
super(plugin, exemptAddresses);
}
@Subscribe(order = PostOrder.LAST)
public void onLogin(LoginEvent event, Continuation continuation) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
if (!event.getResult().isAllowed()) {
return null;
}
if (plugin.getConfiguration().restoreOldKickBehavior()) {
for (String s : plugin.getProxiesIds()) {
if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getPlayer().getUniqueId().toString())) {
event.setResult(ResultedEvent.ComponentResult.denied(serializer.deserialize(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN))));
return null;
}
}
} else if (api.isPlayerOnline(event.getPlayer().getUniqueId())) {
PlayerUtils.setKickedOtherLocation(event.getPlayer().getUniqueId().toString(), unifiedJedis);
api.kickPlayer(event.getPlayer().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION));
}
return null;
} finally {
continuation.resume();
}
}
});
}
@Override
@Subscribe
public void onPostLogin(PostLoginEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
plugin.getUuidTranslator().persistInfo(event.getPlayer().getUsername(), event.getPlayer().getUniqueId(), unifiedJedis);
VelocityPlayerUtils.createVelocityPlayer(event.getPlayer(), unifiedJedis, true);
return null;
}
});
}
@Override
@Subscribe
public void onPlayerDisconnect(DisconnectEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true);
return null;
}
});
}
@Override
@Subscribe
public void onServerChange(ServerConnectedEvent event) {
final String currentServer = event.getServer().getServerInfo().getName();
final String oldServer = event.getPreviousServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", currentServer);
PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer);
return null;
}
});
}
@Override
@Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last
public void onPing(ProxyPingEvent event) {
if (exemptAddresses.contains(event.getConnection().getRemoteAddress().getAddress())) {
return;
}
ServerPing.Builder ping = event.getPing().asBuilder();
ping.onlinePlayers(plugin.getCount());
event.setPing(ping.build());
}
@Override
@Subscribe
public void onPluginMessage(PluginMessageEvent event) {
if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) {
return;
}
event.setResult(ForwardResult.handled());
plugin.executeAsync(() -> {
ByteArrayDataInput in = event.dataAsDataStream();
String subchannel = in.readUTF();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String type;
switch (subchannel) {
case "PlayerList":
out.writeUTF("PlayerList");
Set<UUID> original = Collections.emptySet();
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
original = plugin.getPlayers();
} else {
out.writeUTF(type);
try {
original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) {
}
}
Set<String> players = original.stream()
.map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false))
.collect(Collectors.toSet());
out.writeUTF(Joiner.on(',').join(players));
break;
case "PlayerCount":
out.writeUTF("PlayerCount");
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
out.writeInt(plugin.getCount());
} else {
out.writeUTF(type);
try {
out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size());
} catch (IllegalArgumentException e) {
out.writeInt(0);
}
}
break;
case "LastOnline":
String user = in.readUTF();
out.writeUTF("LastOnline");
out.writeUTF(user);
out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true))));
break;
case "ServerPlayers":
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
boolean includesUsers;
switch (type1) {
case "COUNT":
includesUsers = false;
break;
case "PLAYERS":
includesUsers = true;
break;
default:
// TODO: Should I raise an error?
return;
}
out.writeUTF(type1);
if (includesUsers) {
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : multimap.entries()) {
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
}
serializeMultimap(human, true, out);
} else {
serializeMultiset(multimap.keys(), out);
}
break;
case "Proxy":
out.writeUTF("Proxy");
out.writeUTF(plugin.getConfiguration().getProxyId());
break;
case "PlayerProxy":
String username = in.readUTF();
out.writeUTF("PlayerProxy");
out.writeUTF(username);
out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true))));
break;
default:
return;
}
((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray());
});
}
@Override
@Subscribe
public void onPubSubMessage(PubSubMessageEvent event) {
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) {
String message = event.getMessage();
if (message.startsWith("/"))
message = message.substring(1);
plugin.logInfo("Invoking command via PubSub: /" + message);
((RedisBungeeVelocityPlugin) plugin).getProxy().getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), message);
}
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.UUID;
public class VelocityDataManager extends AbstractDataManager<Player, PostLoginEvent, DisconnectEvent, PubSubMessageEvent> {
public VelocityDataManager(RedisBungeePlugin<Player> plugin) {
super(plugin);
}
@Override
@Subscribe
public void onPostLogin(PostLoginEvent event) {
invalidate(event.getPlayer().getUniqueId());
}
@Override
@Subscribe
public void onPlayerDisconnect(DisconnectEvent event) {
invalidate(event.getPlayer().getUniqueId());
}
@Override
@Subscribe
public void onPubSubMessage(PubSubMessageEvent event) {
handlePubSubMessage(event.getChannel(), event.getMessage());
}
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection();
@Override
public boolean handleKick(UUID target, String message) {
Player player = plugin.getPlayer(target);
if (player == null) {
return false;
}
player.disconnect(serializer.deserialize(message));
return true;
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import redis.clients.jedis.UnifiedJedis;
import java.util.Optional;
public class VelocityPlayerUtils {
protected static void createVelocityPlayer(Player player, UnifiedJedis unifiedJedis, boolean fireEvent) {
Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
String serverName = null;
if (optionalServerConnection.isPresent()) {
serverName = optionalServerConnection.get().getServerInfo().getName();
}
PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, player.getRemoteAddress().getAddress(), fireEvent);
}
}

View File

@@ -1,362 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeVelocityPlugin;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
/**
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
* <p>
* All classes use the {@link AbstractRedisBungeeAPI}.
*
* @author tuxed
* @since 0.2.3
*/
public class RedisBungeeCommands {
private static final Component NO_PLAYER_SPECIFIED =
Component.text("You must specify a player name.", NamedTextColor.RED);
private static final Component PLAYER_NOT_FOUND =
Component.text("No such player found.", NamedTextColor.RED);
private static final Component NO_COMMAND_SPECIFIED =
Component.text("You must specify a command to be run.", NamedTextColor.RED);
private static String playerPlural(int num) {
return num == 1 ? num + " player is" : num + " players are";
}
public static class GlistCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public GlistCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
int count = plugin.getAbstractRedisBungeeApi().getPlayerCount();
Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW);
CommandSource sender = invocation.source();
if (invocation.arguments().length > 0 && invocation.arguments()[0].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
serverToPlayers.forEach((key, value) -> {
// if for any reason UUID translation fails just return the uuid as name, to make command finish executing.
String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false);
human.put(key, playerName != null ? playerName : value.toString());
});
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN);
Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW);
Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers));
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW));
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("velocity.command.server");
}
}
public static class FindCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public FindCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
ServerInfo si = plugin.getProxy().getServer(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid)).map(RegisteredServer::getServerInfo).orElse(null);
if (si != null) {
Component message = Component.text(args[0] + " is on " + si.getName() + ".", NamedTextColor.BLUE);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.find");
}
}
public static class LastSeenCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public LastSeenCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid);
TextComponent.Builder message = Component.text();
if (secs == 0) {
message.color(NamedTextColor.GREEN);
message.content(args[0] + " is currently online.");
} else if (secs != -1) {
message.color(NamedTextColor.BLUE);
message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
} else {
message.color(NamedTextColor.RED);
message.content(args[0] + " has never been online.");
}
sender.sendMessage(message.build());
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.lastseen");
}
}
public static class IpCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public IpCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid);
if (ia != null) {
TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.ip");
}
}
public static class PlayerProxyCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public PlayerProxyCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid);
if (proxy != null) {
TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.pproxy");
}
}
public static class SendToAll implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public SendToAll(RedisBungeeVelocityPlugin plugin) {
//super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
if (args.length > 0) {
String command = Joiner.on(" ").skipNulls().join(args);
plugin.getAbstractRedisBungeeApi().sendProxyCommand(command);
TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(NO_COMMAND_SPECIFIED);
}
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.sendtoall");
}
}
public static class ServerId implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public ServerId(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
invocation.source().sendMessage(Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW));
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.serverid");
}
}
public static class ServerIds implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public ServerIds(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
invocation.source().sendMessage(
Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW));
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.serverids");
}
}
public static class PlistCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public PlistCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId();
if (!plugin.getProxiesIds().contains(proxy)) {
sender.sendMessage(Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED));
return;
}
Set<UUID> players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy);
Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW);
if (args.length >= 2 && args[1].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
serverToPlayers.forEach((key, value) -> {
if (players.contains(value)) {
human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false));
}
});
for (String server : new TreeSet<>(human.keySet())) {
TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED);
TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW);
TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers));
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW));
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.plist");
}
}
}

View File

@@ -3,44 +3,30 @@ import java.io.ByteArrayOutputStream
plugins {
`java-library`
`maven-publish`
id("net.kyori.blossom") version "1.2.0"
alias(libs.plugins.blossom)
alias(libs.plugins.indragit)
}
repositories {
mavenCentral()
}
val jedisVersion = "4.3.2"
val configurateVersion = "3.7.3"
val guavaVersion = "31.1-jre"
dependencies {
api("com.google.guava:guava:$guavaVersion")
api("redis.clients:jedis:$jedisVersion")
api("com.squareup.okhttp:okhttp:2.7.5")
api("org.spongepowered:configurate-yaml:$configurateVersion")
// tests
testImplementation("junit:junit:4.13.2")
api(libs.guava)
api(libs.jedis)
api(libs.okhttp)
api(libs.configurateV3)
api(libs.caffeine)
}
description = "RedisBungee interafaces"
description = "RedisBungee interfaces"
sourceSets {
main {
blossom {
replaceToken("@version@", "$version")
// GIT
var commit: String = ""
val commitStdout = ByteArrayOutputStream()
rootProject.exec {
standardOutput = commitStdout
commandLine("git", "rev-parse", "HEAD")
javaSources {
property("version", "$version")
property("git-commit", indraGit.commit().toString())
}
}
}
commit = "$commitStdout".replace("\n", "") // for some reason it adds new line so remove it.
commitStdout.close()
replaceToken("@git_commit@", commit)
}
java {
@@ -54,25 +40,27 @@ tasks {
val options = options as StandardJavadocDocletOptions
options.use()
options.isDocFilesSubDirs = true
val jedisVersion = libs.jedis.get().version
val configurateVersion = libs.configurateV3.get().version
val guavaVersion = libs.guava.get().version
val adventureVersion = libs.adventure.api.get().version
options.links(
"https://configurate.aoeu.xyz/$configurateVersion/apidocs/", // configurate
"https://javadoc.io/doc/redis.clients/jedis/$jedisVersion/", // jedis
"https://guava.dev/releases/$guavaVersion/api/docs/" // guava
"https://guava.dev/releases/$guavaVersion/api/docs/", // guava
"https://javadoc.io/doc/com.github.ben-manes.caffeine/caffeine",
"https://jd.advntr.dev/api/$adventureVersion"
)
}
test {
useJUnitPlatform()
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(8)
options.release.set(17)
}
javadoc {
options.encoding = Charsets.UTF_8.name()
}
processResources {
filteringCharset = Charsets.UTF_8.name()

View File

@@ -10,21 +10,14 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPooled;
import java.net.InetAddress;
import java.util.*;
@@ -40,21 +33,13 @@ import java.util.*;
public abstract class AbstractRedisBungeeAPI {
protected final RedisBungeePlugin<?> plugin;
private static AbstractRedisBungeeAPI abstractRedisBungeeAPI;
protected final List<String> reservedChannels;
AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) {
// this does make sure that no one can place first initiated API class.
public AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) {
// this does make sure that no one can replace first initiated API class.
if (abstractRedisBungeeAPI == null) {
abstractRedisBungeeAPI = this;
}
this.reservedChannels = ImmutableList.of(
"redisbungee-allservers",
"redisbungee-" + plugin.getConfiguration().getProxyId(),
"redisbungee-data"
);
this.plugin = plugin;
}
/**
@@ -63,7 +48,7 @@ public abstract class AbstractRedisBungeeAPI {
* @return a count of all players found
*/
public final int getPlayerCount() {
return plugin.getCount();
return plugin.proxyDataManager().totalNetworkPlayers();
}
/**
@@ -74,7 +59,7 @@ public abstract class AbstractRedisBungeeAPI {
* @return the last time a player was on, if online returns a 0
*/
public final long getLastOnline(@NonNull UUID player) {
return plugin.getDataManager().getLastOnline(player);
return plugin.playerDataManager().getLastOnline(player);
}
/**
@@ -86,7 +71,7 @@ public abstract class AbstractRedisBungeeAPI {
*/
@Nullable
public final String getServerNameFor(@NonNull UUID player) {
return plugin.getDataManager().getServer(player);
return plugin.playerDataManager().getServerFor(player);
}
/**
@@ -97,7 +82,7 @@ public abstract class AbstractRedisBungeeAPI {
* @return a Set with all players found
*/
public final Set<UUID> getPlayersOnline() {
return plugin.getPlayers();
return plugin.proxyDataManager().networkPlayers();
}
/**
@@ -118,11 +103,11 @@ public abstract class AbstractRedisBungeeAPI {
/**
* Get a full list of players on all servers.
*
* @return a immutable Multimap with all players found on this server
* @return a immutable Multimap with all players found on this network
* @since 0.2.5
*/
public final Multimap<String, UUID> getServerToPlayers() {
return plugin.serverToPlayersCache();
return plugin.playerDataManager().serversToPlayers();
}
/**
@@ -138,11 +123,11 @@ public abstract class AbstractRedisBungeeAPI {
/**
* Get a list of players on the specified proxy.
*
* @param server a server name
* @param proxyID proxy id
* @return a Set with all UUIDs found on this proxy
*/
public final Set<UUID> getPlayersOnProxy(@NonNull String server) {
return plugin.getPlayersOnProxy(server);
public final Set<UUID> getPlayersOnProxy(@NonNull String proxyID) {
return plugin.proxyDataManager().getPlayersOn(proxyID);
}
/**
@@ -163,7 +148,7 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.2.4
*/
public final InetAddress getPlayerIp(@NonNull UUID player) {
return plugin.getDataManager().getIp(player);
return plugin.playerDataManager().getIpFor(player);
}
/**
@@ -174,7 +159,7 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.3.3
*/
public final String getProxy(@NonNull UUID player) {
return plugin.getDataManager().getProxy(player);
return plugin.playerDataManager().getProxyFor(player);
}
/**
@@ -185,7 +170,7 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.2.5
*/
public final void sendProxyCommand(@NonNull String command) {
plugin.sendProxyCommand("allservers", command);
sendProxyCommand("allservers", command);
}
/**
@@ -198,19 +183,20 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.2.5
*/
public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) {
plugin.sendProxyCommand(proxyId, command);
plugin.proxyDataManager().sendCommandTo(proxyId, command);
}
/**
* Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for
* PubSubMessageEvent to fire.
* Sends a message to a PubSub channel which makes PubSubMessageEvent fire.
* <p>
* Note: Since 0.12.0 registering a channel api is no longer required
*
* @param channel The PubSub channel
* @param message the message body to send
* @since 0.3.3
*/
public final void sendChannelMessage(@NonNull String channel, @NonNull String message) {
plugin.sendChannelMessage(channel, message);
plugin.proxyDataManager().sendChannelMessage(channel, message);
}
/**
@@ -221,20 +207,7 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.8.0
*/
public final String getProxyId() {
return plugin.getConfiguration().getProxyId();
}
/**
* Get the current BungeeCord / Velocity proxy ID for this server.
*
* @return the current server ID
* @see #getAllServers()
* @since 0.2.5
* @deprecated to avoid confusion between A server and A proxy see #getProxyId()
*/
@Deprecated
public final String getServerId() {
return getProxyId();
return plugin.proxyDataManager().proxyId();
}
/**
@@ -245,44 +218,7 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.8.0
*/
public final List<String> getAllProxies() {
return plugin.getProxiesIds();
}
/**
* Get all the linked proxies in this network.
*
* @return the list of all proxies
* @see #getServerId()
* @since 0.2.5
* @deprecated to avoid confusion between A server and A proxy see see {@link #getAllProxies()}
*/
@Deprecated
public final List<String> getAllServers() {
return getAllProxies();
}
/**
* Register (a) PubSub channel(s), so that you may handle PubSubMessageEvent for it.
*
* @param channels the channels to register
* @since 0.3
*/
public final void registerPubSubChannels(String... channels) {
plugin.getPubSubListener().addChannel(channels);
}
/**
* Unregister (a) PubSub channel(s).
*
* @param channels the channels to unregister
* @since 0.3
*/
public final void unregisterPubSubChannels(String... channels) {
for (String channel : channels) {
Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel");
}
plugin.getPubSubListener().removeChannel(channels);
return plugin.proxyDataManager().proxiesIds();
}
/**
@@ -353,97 +289,6 @@ public abstract class AbstractRedisBungeeAPI {
}
/**
* Kicks a player from the network
*
* @param playerName player name
* @param message kick message that player will see on kick
* @since 0.8.0
*/
public void kickPlayer(String playerName, String message) {
plugin.kickPlayer(playerName, message);
}
/**
* Kicks a player from the network
*
* @param playerUUID player name
* @param message kick message that player will see on kick
* @since 0.8.0
*/
public void kickPlayer(UUID playerUUID, String message) {
plugin.kickPlayer(playerUUID, message);
}
/**
* This gives you instance of Jedis
*
* @return {@link Jedis}
* @throws IllegalStateException if the {@link #getMode()} is not equal to {@link RedisBungeeMode#SINGLE}
* @see #getJedisPool()
* @since 0.7.0
*/
public Jedis requestJedis() {
if (getMode() == RedisBungeeMode.SINGLE) {
return getJedisPool().getResource();
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.SINGLE);
}
}
/**
* This gets Redis Bungee {@link JedisPool}
*
* @return {@link JedisPool}
* @throws IllegalStateException if the {@link #getMode()} is not equal to {@link RedisBungeeMode#SINGLE}
* @throws IllegalStateException if JedisPool compatibility mode is disabled in the config
* @since 0.6.5
*/
public JedisPool getJedisPool() {
if (getMode() == RedisBungeeMode.SINGLE) {
JedisPool jedisPool = ((JedisPooledSummoner) this.plugin.getSummoner()).getCompatibilityJedisPool();
if (jedisPool == null) {
throw new IllegalStateException("JedisPool compatibility mode is disabled");
}
return jedisPool;
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.SINGLE);
}
}
/**
* This gives you an instance of JedisCluster that can't be closed
* see {@link com.imaginarycode.minecraft.redisbungee.api.summoners.NotClosableJedisCluster}
*
* @return {@link redis.clients.jedis.JedisCluster}
* @throws IllegalStateException if the {@link #getMode()} is not equal to {@link RedisBungeeMode#CLUSTER}
* @since 0.8.0
*/
public JedisCluster requestClusterJedis() {
if (getMode() == RedisBungeeMode.CLUSTER) {
return ((JedisClusterSummoner) this.plugin.getSummoner()).obtainResource();
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.CLUSTER);
}
}
/**
* This gives you an instance of JedisPooled that can't be closed
* see {@link com.imaginarycode.minecraft.redisbungee.api.summoners.NotClosableJedisPooled}
*
* @return {@link redis.clients.jedis.JedisPooled}
* @throws IllegalStateException if the {@link #getMode()} is not equal to {@link RedisBungeeMode#SINGLE}
* @since 0.8.0
*/
public JedisPooled requestJedisPooled() {
if (getMode() == RedisBungeeMode.SINGLE) {
return ((JedisPooledSummoner) this.plugin.getSummoner()).obtainResource();
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.SINGLE);
}
}
/**
* returns Summoner class responsible for Single Jedis {@link redis.clients.jedis.JedisPooled} with {@link JedisPool}, Cluster Jedis {@link redis.clients.jedis.JedisCluster} handling
*
@@ -454,9 +299,32 @@ public abstract class AbstractRedisBungeeAPI {
return this.plugin.getSummoner();
}
/**
* Kicks a player from the network using miniMessage
* calls {@link #getUuidFromName(String)} to get uuid
* <a href="https://docs.advntr.dev/minimessage/format.html">...</a>
* @param playerName player name
* @param miniMessage kick message that player will see on kick using minimessage as format
* @since 0.13.0
*/
public void kickPlayer(String playerName, String miniMessage) {
kickPlayer(getUuidFromName(playerName), miniMessage);
}
/**
* Kicks a player from the network
* <a href="https://docs.advntr.dev/minimessage/format.html">...</a>
* @param player player uuid
* @param miniMessage kick message that player will see on kick using minimessage as format
* @since 0.13.0
*/
public void kickPlayer(UUID player, String miniMessage) {
plugin.playerDataManager().serializedPlayerKick(player, miniMessage);
}
/**
* shows what mode is RedisBungee is on
* Basically what every redis mode is used like cluster or single instance.
*
* @return {@link RedisBungeeMode}
* @since 0.8.0

View File

@@ -13,11 +13,11 @@ package com.imaginarycode.minecraft.redisbungee;
public class Constants {
public final static String VERSION = "@version@";
public final static String GIT_COMMIT = "@git_commit@";
public final static String VERSION = "{{ version }}";
public final static String GIT_COMMIT = "{{ git-commit }}";
public static String getGithubCommitLink() {
return "https://github.com/ProxioDev/RedisBungee/commit/" + GIT_COMMIT;
return "https://github.com/ProxioDev/ValioBungee/commit/" + GIT_COMMIT;
}
}

View File

@@ -0,0 +1,286 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.net.InetAddresses;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask;
import org.json.JSONObject;
import redis.clients.jedis.ClusterPipeline;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
public abstract class PlayerDataManager<P> {
protected final RedisBungeePlugin<P> plugin;
private final Object SERVERS_TO_PLAYERS_KEY = new Object();
private final UnifiedJedis unifiedJedis;
private final String proxyId;
private final String networkId;
private final LoadingCache<UUID, String> serverCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis);
private final LoadingCache<UUID, String> lastServerCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastServerFromRedis);
private final LoadingCache<UUID, String> proxyCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getProxyFromRedis);
private final LoadingCache<UUID, InetAddress> ipCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getIpAddressFromRedis);
private final LoadingCache<UUID, Long> lastOnlineCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastOnlineFromRedis);
private final LoadingCache<Object, Multimap<String, UUID>> serverToPlayersCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(this::serversToPlayersBuilder);
public PlayerDataManager(RedisBungeePlugin<P> plugin) {
this.plugin = plugin;
this.unifiedJedis = plugin.proxyDataManager().unifiedJedis();
this.proxyId = plugin.proxyDataManager().proxyId();
this.networkId = plugin.proxyDataManager().networkId();
}
// handle network wide
// server change
// public void onPlayerChangedServerNetworkEvent
// public void onNetworkPlayerQuit
// public void onNetworkPlayerJoin
// local events
// public void onPubSubMessageEvent
// public void onServerConnectedEvent
// public void onLoginEvent
// public void onDisconnectEvent
protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) {
this.serverCache.invalidate(event.getUuid());
this.lastServerCache.invalidate(event.getUuid());
//TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache in-place without querying redis. That would be a lot more efficient.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) {
this.proxyCache.invalidate(event.getUuid());
this.serverCache.invalidate(event.getUuid());
this.ipCache.invalidate(event.getUuid());
this.lastOnlineCache.invalidate(event.getUuid());
//TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache in-place without querying redis. That would be a lot more efficient.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handleNetworkPlayerJoin(IPlayerJoinedNetworkEvent event) {
this.proxyCache.invalidate(event.getUuid());
this.serverCache.invalidate(event.getUuid());
this.ipCache.invalidate(event.getUuid());
this.lastOnlineCache.invalidate(event.getUuid());
//TODO: We could also rely on redisbungee-serverchange pubsub messages to update the cache in-place without querying redis. That would be a lot more efficient.
this.serverToPlayersCache.invalidate(SERVERS_TO_PLAYERS_KEY);
}
protected void handlePubSubMessageEvent(IPubSubMessageEvent event) {
switch (event.getChannel()) {
case "redisbungee-serverchange" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
String from = null;
if (data.has("from")) from = data.getString("from");
String to = data.getString("to");
plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
}
case "redisbungee-player-join" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid));
}
case "redisbungee-player-leave" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
}
case "redisbungee-player-kick" -> {
JSONObject data = new JSONObject(event.getMessage());
UUID uuid = UUID.fromString(data.getString("uuid"));
String message = data.getString("serialized-message");
handleSerializedKick(uuid, message);
}
}
}
protected void playerChangedServer(UUID uuid, String from, String to) {
JSONObject data = new JSONObject();
data.put("proxy", this.proxyId);
data.put("uuid", uuid);
data.put("from", from);
data.put("to", to);
plugin.proxyDataManager().sendChannelMessage("redisbungee-serverchange", data.toString());
plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
handleServerChangeRedis(uuid, to);
}
// must check if player is on the local proxy
// https://docs.advntr.dev/minimessage/index.html
// implemented downstream in Velocity and Bungeecord
protected abstract boolean handleSerializedKick(UUID player, String serializedMiniMessage);
// https://docs.advntr.dev/minimessage/index.html
// implemented downstream in Velocity and Bungeecord
// called by kickPlayer in each impl of this class `NOT OVERRIDE`
public void serializedPlayerKick(UUID player, String serializedMiniMessage) {
JSONObject data = new JSONObject();
data.put("proxy", this.proxyId);
data.put("uuid", player);
data.put("serialized-message", serializedMiniMessage);
if (!handleSerializedKick(player, serializedMiniMessage))
plugin.proxyDataManager().sendChannelMessage("redisbungee-player-kick", data.toString());
}
private void handleServerChangeRedis(UUID uuid, String server) {
Map<String, String> data = new HashMap<>();
data.put("server", server);
data.put("last-server", server);
unifiedJedis.hset("redisbungee::" + this.networkId + "::player::" + uuid + "::data", data);
}
protected void addPlayer(final UUID uuid, final String name, final InetAddress inetAddress) {
Map<String, String> redisData = new HashMap<>();
redisData.put("last-online", String.valueOf(0));
redisData.put("proxy", this.proxyId);
redisData.put("ip", inetAddress.getHostAddress());
unifiedJedis.hset("redisbungee::" + this.networkId + "::player::" + uuid + "::data", redisData);
plugin.getUuidTranslator().persistInfo(name, uuid, this.unifiedJedis);
JSONObject data = new JSONObject();
data.put("proxy", this.proxyId);
data.put("uuid", uuid);
plugin.proxyDataManager().sendChannelMessage("redisbungee-player-join", data.toString());
plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid));
this.plugin.proxyDataManager().addPlayer(uuid);
}
protected void removePlayer(UUID uuid) {
unifiedJedis.hset("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-online", String.valueOf(System.currentTimeMillis()));
unifiedJedis.hdel("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "server", "proxy", "ip");
JSONObject data = new JSONObject();
data.put("proxy", this.proxyId);
data.put("uuid", uuid);
plugin.proxyDataManager().sendChannelMessage("redisbungee-player-leave", data.toString());
plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
this.plugin.proxyDataManager().removePlayer(uuid);
}
protected String getProxyFromRedis(UUID uuid) {
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "proxy");
}
protected String getServerFromRedis(UUID uuid) {
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "server");
}
protected String getLastServerFromRedis(UUID uuid) {
return unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-server");
}
protected InetAddress getIpAddressFromRedis(UUID uuid) {
String ip = unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "ip");
if (ip == null) return null;
return InetAddresses.forString(ip);
}
protected long getLastOnlineFromRedis(UUID uuid) {
String unixString = unifiedJedis.hget("redisbungee::" + this.networkId + "::player::" + uuid + "::data", "last-online");
if (unixString == null) return -1;
return Long.parseLong(unixString);
}
public String getLastServerFor(UUID uuid) {
return this.lastServerCache.get(uuid);
}
public String getServerFor(UUID uuid) {
return this.serverCache.get(uuid);
}
public String getProxyFor(UUID uuid) {
return this.proxyCache.get(uuid);
}
public InetAddress getIpFor(UUID uuid) {
return this.ipCache.get(uuid);
}
public long getLastOnline(UUID uuid) {
return this.lastOnlineCache.get(uuid);
}
public Multimap<String, UUID> serversToPlayers() {
return this.serverToPlayersCache.get(SERVERS_TO_PLAYERS_KEY);
}
protected Multimap<String, UUID> serversToPlayersBuilder(Object o) {
try {
return new RedisPipelineTask<Multimap<String, UUID>>(plugin) {
private final Set<UUID> uuids = plugin.proxyDataManager().networkPlayers();
private final ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
@Override
public Multimap<String, UUID> doPooledPipeline(Pipeline pipeline) {
HashMap<UUID, Response<String>> responses = new HashMap<>();
for (UUID uuid : uuids) {
Optional.ofNullable(pipeline.hget("redisbungee::" + networkId + "::player::" + uuid + "::data", "server")).ifPresent(stringResponse -> {
responses.put(uuid, stringResponse);
});
}
pipeline.sync();
responses.forEach((uuid, response) -> {
String key = response.get();
if (key == null) return;
builder.put(key, uuid);
});
return builder.build();
}
@Override
public Multimap<String, UUID> clusterPipeline(ClusterPipeline pipeline) {
HashMap<UUID, Response<String>> responses = new HashMap<>();
for (UUID uuid : uuids) {
Optional.ofNullable(pipeline.hget("redisbungee::" + networkId + "::player::" + uuid + "::data", "server")).ifPresent(stringResponse -> {
responses.put(uuid, stringResponse);
});
}
pipeline.sync();
responses.forEach((uuid, response) -> {
String key = response.get();
if (key == null) return;
builder.put(key, uuid);
});
return builder.build();
}
}.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,408 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
import com.imaginarycode.minecraft.redisbungee.api.payloads.gson.AbstractPayloadSerializer;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.DeathPayloadSerializer;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.HeartbeatPayloadSerializer;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.PubSubPayloadSerializer;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.RunCommandPayloadSerializer;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask;
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
import redis.clients.jedis.*;
import redis.clients.jedis.params.XAddParams;
import redis.clients.jedis.params.XReadParams;
import redis.clients.jedis.resps.StreamEntry;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.google.common.base.Preconditions.checkArgument;
public abstract class ProxyDataManager implements Runnable {
private static final int MAX_ENTRIES = 10000;
private final AtomicBoolean closed = new AtomicBoolean(false);
private final UnifiedJedis unifiedJedis;
// data:
// Proxy id, heartbeat (unix epoch from instant), players as int
private final ConcurrentHashMap<String, HeartbeatPayload.HeartbeatData> heartbeats = new ConcurrentHashMap<>();
private final String networkId;
private final String proxyId;
private final String STREAM_ID;
// This different from proxy id, just to detect if there is duplicate proxy using same proxy id
private final UUID dataManagerUUID = UUID.randomUUID();
protected final RedisBungeePlugin<?> plugin;
private final Gson gson = new GsonBuilder().registerTypeAdapter(AbstractPayload.class, new AbstractPayloadSerializer()).registerTypeAdapter(HeartbeatPayload.class, new HeartbeatPayloadSerializer()).registerTypeAdapter(DeathPayload.class, new DeathPayloadSerializer()).registerTypeAdapter(PubSubPayload.class, new PubSubPayloadSerializer()).registerTypeAdapter(RunCommandPayload.class, new RunCommandPayloadSerializer()).create();
public ProxyDataManager(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
this.proxyId = this.plugin.configuration().getProxyId();
this.unifiedJedis = plugin.getSummoner().obtainResource();
this.networkId = plugin.configuration().networkId();
this.STREAM_ID = "network-" + this.networkId + "-redisbungee-stream";
this.destroyProxyMembers();
}
public abstract Set<UUID> getLocalOnlineUUIDs();
public Set<UUID> getPlayersOn(String proxyId) {
checkArgument(proxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID");
if (proxyId.equals(this.proxyId)) return this.getLocalOnlineUUIDs();
if (!this.heartbeats.containsKey(proxyId)) {
return new HashSet<>(); // return empty hashset or null?
}
return getProxyMembers(proxyId);
}
// this skip checking if proxy is and its package private
// due proxy shutdown shenanigans
public boolean isPlayerTrulyOnProxy(String proxyId, UUID uuid) {
return unifiedJedis.sismember("redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players", uuid.toString());
}
public List<String> proxiesIds() {
return Collections.list(this.heartbeats.keys());
}
public synchronized void sendCommandTo(String proxyToRun, String command) {
if (isClosed()) return;
if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) {
handlePlatformCommandExecution(command);
}
publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command));
}
public synchronized void sendChannelMessage(String channel, String message) {
if (isClosed()) return;
publishPayload(new PubSubPayload(this.proxyId, channel, message));
}
// call every 1 second
public synchronized void publishHeartbeat() {
if (isClosed()) return;
HeartbeatPayload.HeartbeatData heartbeatData = new HeartbeatPayload.HeartbeatData(Instant.now().getEpochSecond(), this.getLocalOnlineUUIDs().size());
this.heartbeats.put(this.proxyId(), heartbeatData);
publishPayload(new HeartbeatPayload(this.proxyId, heartbeatData));
}
public Set<UUID> networkPlayers() {
try {
return new RedisPipelineTask<Set<UUID>>(this.plugin) {
@Override
public Set<UUID> doPooledPipeline(Pipeline pipeline) {
HashSet<Response<Set<String>>> responses = new HashSet<>();
for (String proxyId : proxiesIds()) {
responses.add(pipeline.smembers("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players"));
}
pipeline.sync();
HashSet<UUID> uuids = new HashSet<>();
for (Response<Set<String>> response : responses) {
for (String stringUUID : response.get()) {
uuids.add(UUID.fromString(stringUUID));
}
}
return uuids;
}
@Override
public Set<UUID> clusterPipeline(ClusterPipeline pipeline) {
HashSet<Response<Set<String>>> responses = new HashSet<>();
for (String proxyId : proxiesIds()) {
responses.add(pipeline.smembers("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players"));
}
pipeline.sync();
HashSet<UUID> uuids = new HashSet<>();
for (Response<Set<String>> response : responses) {
for (String stringUUID : response.get()) {
uuids.add(UUID.fromString(stringUUID));
}
}
return uuids;
}
}.call();
} catch (Exception e) {
throw new RuntimeException("unable to get network players", e);
}
}
public int totalNetworkPlayers() {
int players = 0;
for (HeartbeatPayload.HeartbeatData value : this.heartbeats.values()) {
players += value.players();
}
return players;
}
public Map<String, Integer> eachProxyCount() {
ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
heartbeats.forEach((proxy, data) -> builder.put(proxy, data.players()));
return builder.build();
}
// Call on close
private synchronized void publishDeath() {
publishPayload(new DeathPayload(this.proxyId));
}
private void publishPayload(AbstractPayload payload) {
Map<String, String> data = new HashMap<>();
data.put("payload", gson.toJson(payload));
data.put("data-manager-uuid", this.dataManagerUUID.toString());
data.put("class", payload.getClassName());
this.unifiedJedis.xadd(STREAM_ID, XAddParams.xAddParams().maxLen(MAX_ENTRIES).id(StreamEntryID.NEW_ENTRY), data);
}
private void handleHeartBeat(HeartbeatPayload payload) {
String id = payload.senderProxy();
if (!heartbeats.containsKey(id)) {
plugin.logInfo("Proxy {} has connected", id);
}
heartbeats.put(id, payload.data());
}
// call every 1 minutes
public void correctionTask() {
// let's check this proxy players
Set<UUID> localOnlineUUIDs = getLocalOnlineUUIDs();
Set<UUID> storedRedisUuids = getProxyMembers(this.proxyId);
if (!localOnlineUUIDs.equals(storedRedisUuids)) {
plugin.logWarn("De-synced players set detected correcting....");
Set<UUID> add = new HashSet<>(localOnlineUUIDs);
Set<UUID> remove = new HashSet<>(storedRedisUuids);
add.removeAll(storedRedisUuids);
remove.removeAll(localOnlineUUIDs);
for (UUID uuid : add) {
plugin.logWarn("found {} that isn't in the set, adding it to the Corrected set", uuid);
}
for (UUID uuid : remove) {
plugin.logWarn("found {} that does not belong to this proxy removing it from the corrected set", uuid);
}
try {
new RedisPipelineTask<Void>(plugin) {
@Override
public Void doPooledPipeline(Pipeline pipeline) {
Set<String> removeString = new HashSet<>();
for (UUID uuid : remove) {
removeString.add(uuid.toString());
}
Set<String> addString = new HashSet<>();
for (UUID uuid : add) {
addString.add(uuid.toString());
}
pipeline.srem("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", removeString.toArray(new String[]{}));
pipeline.sadd("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", addString.toArray(new String[]{}));
pipeline.sync();
return null;
}
@Override
public Void clusterPipeline(ClusterPipeline pipeline) {
Set<String> removeString = new HashSet<>();
for (UUID uuid : remove) {
removeString.add(uuid.toString());
}
Set<String> addString = new HashSet<>();
for (UUID uuid : add) {
addString.add(uuid.toString());
}
pipeline.srem("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", removeString.toArray(new String[]{}));
pipeline.sadd("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", addString.toArray(new String[]{}));
pipeline.sync();
return null;
}
}.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
plugin.logInfo("Player set has been corrected!");
}
// handle dead proxies "THAT" Didn't send death payload but considered dead due TIMEOUT ~30 seconds
final Set<String> deadProxies = new HashSet<>();
for (Map.Entry<String, HeartbeatPayload.HeartbeatData> stringHeartbeatDataEntry : this.heartbeats.entrySet()) {
String id = stringHeartbeatDataEntry.getKey();
long heartbeat = stringHeartbeatDataEntry.getValue().heartbeat();
if (Instant.now().getEpochSecond() - heartbeat > RedisUtil.PROXY_TIMEOUT) {
deadProxies.add(id);
cleanProxy(id);
}
}
try {
new RedisPipelineTask<Void>(plugin) {
@Override
public Void doPooledPipeline(Pipeline pipeline) {
for (String deadProxy : deadProxies) {
pipeline.del("redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players");
}
pipeline.sync();
return null;
}
@Override
public Void clusterPipeline(ClusterPipeline pipeline) {
for (String deadProxy : deadProxies) {
pipeline.del("redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players");
}
pipeline.sync();
return null;
}
}.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void handleProxyDeath(DeathPayload payload) {
cleanProxy(payload.senderProxy());
}
private void cleanProxy(String id) {
if (id.equals(this.proxyId())) {
return;
}
for (UUID uuid : getProxyMembers(id)) plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
this.heartbeats.remove(id);
plugin.logInfo("Proxy {} has disconnected", id);
}
private void handleChannelMessage(PubSubPayload payload) {
String channel = payload.channel();
String message = payload.message();
this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message));
}
protected abstract void handlePlatformCommandExecution(String command);
private void handleCommand(RunCommandPayload payload) {
String proxyToRun = payload.proxyToRun();
String command = payload.command();
if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) {
handlePlatformCommandExecution(command);
}
}
public void addPlayer(UUID uuid) {
this.unifiedJedis.sadd("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players", uuid.toString());
}
public void removePlayer(UUID uuid) {
this.unifiedJedis.srem("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players", uuid.toString());
}
private void destroyProxyMembers() {
unifiedJedis.del("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players");
}
private Set<UUID> getProxyMembers(String proxyId) {
Set<String> uuidsStrings = unifiedJedis.smembers("redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players");
HashSet<UUID> uuids = new HashSet<>();
for (String proxyMember : uuidsStrings) {
uuids.add(UUID.fromString(proxyMember));
}
return uuids;
}
private StreamEntryID lastStreamEntryID;
// polling from stream
@Override
public void run() {
while (!isClosed()) {
try {
List<java.util.Map.Entry<String, List<StreamEntry>>> data = unifiedJedis.xread(XReadParams.xReadParams().block(0), Collections.singletonMap(STREAM_ID, lastStreamEntryID != null ? lastStreamEntryID : StreamEntryID.LAST_ENTRY));
for (Map.Entry<String, List<StreamEntry>> datum : data) {
for (StreamEntry streamEntry : datum.getValue()) {
this.lastStreamEntryID = streamEntry.getID();
String payloadData = streamEntry.getFields().get("payload");
String clazz = streamEntry.getFields().get("class");
UUID payloadDataManagerUUID = UUID.fromString(streamEntry.getFields().get("data-manager-uuid"));
AbstractPayload unknownPayload = (AbstractPayload) gson.fromJson(payloadData, Class.forName(clazz));
if (unknownPayload.senderProxy().equals(this.proxyId)) {
if (!payloadDataManagerUUID.equals(this.dataManagerUUID)) {
plugin.logWarn("detected other proxy is using same ID! {} this can cause issues, please shutdown this proxy and change the id!", this.proxyId);
}
continue;
}
if (unknownPayload instanceof HeartbeatPayload payload) {
handleHeartBeat(payload);
} else if (unknownPayload instanceof DeathPayload payload) {
handleProxyDeath(payload);
} else if (unknownPayload instanceof RunCommandPayload payload) {
handleCommand(payload);
} else if (unknownPayload instanceof PubSubPayload payload) {
handleChannelMessage(payload);
} else {
plugin.logWarn("got unknown data manager payload: {}", unknownPayload.getClassName());
}
}
}
} catch (Exception e) {
this.plugin.logFatal("an error has occurred in the stream", e);
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
}
}
}
public void close() {
closed.set(true);
this.publishDeath();
this.heartbeats.clear();
this.destroyProxyMembers();
}
public boolean isClosed() {
return closed.get();
}
public String proxyId() {
return proxyId;
}
public UnifiedJedis unifiedJedis() {
return unifiedJedis;
}
public String networkId() {
return networkId;
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import java.net.InetAddress;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* This Class has all internal methods needed by every redis bungee plugin, and it can be used to implement another platforms than bungeecord or another forks of RedisBungee
* <p>
* Reason this is interface because some proxies implementations require the user to extend class for plugins for example bungeecord.
*
* @author Ham1255
* @since 0.7.0
*/
public interface RedisBungeePlugin<P> extends EventsPlatform {
default void initialize() {
}
default void stop() {
}
void logInfo(String msg);
void logInfo(String format, Object... object);
void logWarn(String msg);
void logWarn(String format, Object... object);
void logFatal(String msg);
void logFatal(String format, Throwable throwable);
RedisBungeeConfiguration configuration();
Summoner<?> getSummoner();
RedisBungeeMode getRedisBungeeMode();
AbstractRedisBungeeAPI getAbstractRedisBungeeApi();
ProxyDataManager proxyDataManager();
PlayerDataManager<P> playerDataManager();
UUIDTranslator getUuidTranslator();
boolean isOnlineMode();
P getPlayer(UUID uuid);
P getPlayer(String name);
UUID getPlayerUUID(String player);
String getPlayerName(UUID player);
String getPlayerServerName(P player);
boolean isPlayerOnAServer(P player);
InetAddress getPlayerIp(P player);
void executeAsync(Runnable runnable);
void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time);
String platformId();
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.config;
public enum HandleMotdOrder {
FIRST,
NORMAL,
LAST
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.config;
import com.google.common.collect.ImmutableList;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import java.util.List;
public class RedisBungeeConfiguration {
private final String proxyId;
private final List<InetAddress> exemptAddresses;
private final boolean kickWhenOnline;
private final boolean handleReconnectToLastServer;
private final boolean handleMotd;
private final HandleMotdOrder handleMotdOrder;
private final CommandsConfiguration commandsConfiguration;
private final String networkId;
public RedisBungeeConfiguration(String networkId, String proxyId, List<String> exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd, HandleMotdOrder handleMotdOrder, CommandsConfiguration commandsConfiguration) {
this.proxyId = proxyId;
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
for (String s : exemptAddresses) {
addressBuilder.add(InetAddresses.forString(s));
}
this.exemptAddresses = addressBuilder.build();
this.kickWhenOnline = kickWhenOnline;
this.handleReconnectToLastServer = handleReconnectToLastServer;
this.handleMotd = handleMotd;
this.handleMotdOrder = handleMotdOrder;
this.commandsConfiguration = commandsConfiguration;
this.networkId = networkId;
}
public String getProxyId() {
return proxyId;
}
public List<InetAddress> getExemptAddresses() {
return exemptAddresses;
}
public boolean kickWhenOnline() {
return kickWhenOnline;
}
public boolean handleMotd() {
return this.handleMotd;
}
public HandleMotdOrder handleMotdOrder() {
return handleMotdOrder;
}
public boolean handleReconnectToLastServer() {
return this.handleReconnectToLastServer;
}
public record CommandsConfiguration(boolean redisbungeeEnabled, boolean redisbungeeLegacyEnabled,
LegacySubCommandsConfiguration legacySubCommandsConfiguration) {
}
public record LegacySubCommandsConfiguration(boolean findEnabled, boolean glistEnabled, boolean ipEnabled,
boolean lastseenEnabled, boolean plistEnabled, boolean pproxyEnabled,
boolean sendtoallEnabled, boolean serveridEnabled,
boolean serveridsEnabled, boolean installFind, boolean installGlist, boolean installIp,
boolean installLastseen, boolean installPlist, boolean installPproxy,
boolean installSendtoall, boolean installServerid,
boolean installServerids) {
}
public CommandsConfiguration commandsConfiguration() {
return commandsConfiguration;
}
public String networkId() {
return networkId;
}
}

View File

@@ -8,13 +8,14 @@
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.config;
package com.imaginarycode.minecraft.redisbungee.api.config.loaders;
import com.google.common.collect.ImmutableMap;
import com.google.common.reflect.TypeToken;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.HandleMotdOrder;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
@@ -26,35 +27,29 @@ import redis.clients.jedis.*;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import redis.clients.jedis.providers.PooledConnectionProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
public interface ConfigLoader {
public interface ConfigLoader extends GenericConfigLoader {
default void loadConfig(RedisBungeePlugin<?> plugin, File dataFolder) throws IOException {
loadConfig(plugin, dataFolder.toPath());
}
int CONFIG_VERSION = 2;
default void loadConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
Path configFile = createConfigFile(dataFolder);
Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml");
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build();
ConfigurationNode node = yamlConfigurationFileLoader.load();
if (node.getNode("config-version").getInt(0) != RedisBungeeConfiguration.CONFIG_VERSION) {
handleOldConfig(dataFolder);
if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) {
handleOldConfig(dataFolder, "config.yml", "config.yml");
node = yamlConfigurationFileLoader.load();
}
final boolean useSSL = node.getNode("useSSL").getBoolean(false);
final boolean overrideBungeeCommands = node.getNode("override-bungee-commands").getBoolean(false);
final boolean registerLegacyCommands = node.getNode("register-legacy-commands").getBoolean(false);
final boolean restoreOldKickBehavior = node.getNode("disable-kick-when-online").getBoolean(false);
final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true);
String redisPassword = node.getNode("redis-password").getString("");
String redisUsername = node.getNode("redis-username").getString("");
String proxyId = node.getNode("proxy-id").getString("test-1");
String networkId = node.getNode("network-id").getString("main");
String proxyId = node.getNode("proxy-id").getString("proxy-1");
final int maxConnections = node.getNode("max-redis-connections").getInt(10);
List<String> exemptAddresses;
try {
@@ -71,10 +66,19 @@ public interface ConfigLoader {
if ((redisUsername.isEmpty() || redisUsername.equals("none"))) {
redisUsername = null;
}
if (useSSL) {
plugin.logInfo("Using ssl");
// env var
String proxyIdFromEnv = System.getenv("REDISBUNGEE_PROXY_ID");
if (proxyIdFromEnv != null) {
plugin.logInfo("Overriding current configured proxy id {} and been set to {} by Environment variable REDISBUNGEE_PROXY_ID", proxyId, proxyIdFromEnv);
proxyId = proxyIdFromEnv;
}
String networkIdFromEnv = System.getenv("REDISBUNGEE_NETWORK_ID");
if (networkIdFromEnv != null) {
plugin.logInfo("Overriding current configured network id {} and been set to {} by Environment variable REDISBUNGEE_NETWORK_ID", networkId, networkIdFromEnv);
networkId = networkIdFromEnv;
}
// Configuration sanity checks.
if (proxyId == null || proxyId.isEmpty()) {
String genId = UUID.randomUUID().toString();
@@ -86,9 +90,74 @@ public interface ConfigLoader {
} else {
plugin.logInfo("Loaded proxy id " + proxyId);
}
RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, getMessagesFromPath(createMessagesFile(dataFolder)), restoreOldKickBehavior);
if (networkId.isEmpty()) {
networkId = "main";
plugin.logWarn("network id was empty and replaced with 'main'");
}
plugin.logInfo("Loaded network id " + networkId);
// TO avoid proxies from different platforms from seeing each other.
networkId = plugin.platformId() + "-" + networkId;
plugin.logInfo("Platform is {} so network id is now is {}", plugin.platformId(), networkId);
boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean();
boolean handleMotd = node.getNode("handle-motd").getBoolean(true);
plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer);
plugin.logInfo("handle motd: {}", handleMotd);
HandleMotdOrder handleMotdOrder = HandleMotdOrder.NORMAL;
String handleMotdOrderName = node.getNode("handle-motd-priority").getString();
if (handleMotdOrderName != null) {
try {
handleMotdOrder = HandleMotdOrder.valueOf(handleMotdOrderName.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
plugin.logWarn("handle motd order value '{}' is unsupported (allowed: {})", handleMotdOrderName, HandleMotdOrder.values());
}
}
plugin.logInfo("handle motd order: {}", handleMotdOrder);
// commands
boolean redisBungeeEnabled = node.getNode("commands", "redisbungee", "enabled").getBoolean(true);
boolean redisBungeeLegacyEnabled =node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(false);
boolean glistEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled").getBoolean(false);
boolean findEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled").getBoolean(false);
boolean lastseenEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled").getBoolean(false);
boolean ipEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled").getBoolean(false);
boolean pproxyEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled").getBoolean(false);
boolean sendToAllEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled").getBoolean(false);
boolean serverIdEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled").getBoolean(false);
boolean serverIdsEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled").getBoolean(false);
boolean pListEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled").getBoolean(false);
boolean installGlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install").getBoolean(false);
boolean installFind = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install").getBoolean(false);
boolean installLastseen = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install").getBoolean(false);
boolean installIp = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install").getBoolean(false);
boolean installPproxy = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install").getBoolean(false);
boolean installSendToAll = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install").getBoolean(false);
boolean installServerid = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install").getBoolean(false);
boolean installServerIds = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install").getBoolean(false);
boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(false);
RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(networkId, proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, handleMotdOrder,
new RedisBungeeConfiguration.CommandsConfiguration(
redisBungeeEnabled, redisBungeeLegacyEnabled,
new RedisBungeeConfiguration.LegacySubCommandsConfiguration(
findEnabled, glistEnabled, ipEnabled,
lastseenEnabled, pListEnabled, pproxyEnabled,
sendToAllEnabled, serverIdEnabled, serverIdsEnabled,
installFind, installGlist, installIp,
installLastseen, installPlist, installPproxy,
installSendToAll, installServerid, installServerIds)
));
Summoner<?> summoner;
RedisBungeeMode redisBungeeMode;
if (useSSL) {
plugin.logInfo("Using ssl");
}
if (node.getNode("cluster-mode-enabled").getBoolean(false)) {
plugin.logInfo("RedisBungee MODE: CLUSTER");
Set<HostAndPort> hostAndPortSet = new HashSet<>();
@@ -115,7 +184,7 @@ public interface ConfigLoader {
throw new RuntimeException("No redis server specified");
}
JedisPool jedisPool = null;
if (node.getNode("enable-jedis-pool-compatibility").getBoolean(true)) {
if (node.getNode("enable-jedis-pool-compatibility").getBoolean(false)) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3));
config.setBlockWhenExhausted(true);
@@ -134,53 +203,5 @@ public interface ConfigLoader {
void onConfigLoad(RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode);
default ImmutableMap<RedisBungeeConfiguration.MessageType, String> getMessagesFromPath(Path path) throws IOException {
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(path).build();
ConfigurationNode node = yamlConfigurationFileLoader.load();
HashMap<RedisBungeeConfiguration.MessageType, String> messages = new HashMap<>();
messages.put(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION, node.getNode("logged-in-other-location").getString("§cLogged in from another location."));
messages.put(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN, node.getNode("already-logged-in").getString("§cYou are already logged in!"));
return ImmutableMap.copyOf(messages);
}
default Path createMessagesFile(Path dataFolder) throws IOException {
if (Files.notExists(dataFolder)) {
Files.createDirectory(dataFolder);
}
Path file = dataFolder.resolve("messages.yml");
if (Files.notExists(file)) {
try (InputStream in = getClass().getClassLoader().getResourceAsStream("messages.yml")) {
Files.createFile(file);
assert in != null;
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
}
}
return file;
}
default Path createConfigFile(Path dataFolder) throws IOException {
if (Files.notExists(dataFolder)) {
Files.createDirectory(dataFolder);
}
Path file = dataFolder.resolve("config.yml");
if (Files.notExists(file)) {
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) {
Files.createFile(file);
assert in != null;
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
}
}
return file;
}
default void handleOldConfig(Path dataFolder) throws IOException {
Path oldConfigFolder = dataFolder.resolve("old_config");
if (Files.notExists(oldConfigFolder)) {
Files.createDirectory(oldConfigFolder);
}
Path oldConfigPath = dataFolder.resolve("config.yml");
Files.move(oldConfigPath, oldConfigFolder.resolve(UUID.randomUUID() + "_config.yml"));
createConfigFile(dataFolder);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.config.loaders;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
public interface GenericConfigLoader {
// CHANGES on every reboot
String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond();
default Path createConfigFile(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException {
if (Files.notExists(dataFolder)) {
Files.createDirectory(dataFolder);
}
Path file = dataFolder.resolve(configFile);
if (Files.notExists(file) && defaultResourceID != null) {
try (InputStream in = getClass().getClassLoader().getResourceAsStream(defaultResourceID)) {
Files.createFile(file);
assert in != null;
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
}
}
return file;
}
default void handleOldConfig(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException {
Path oldConfigFolder = dataFolder.resolve("old_config");
if (Files.notExists(oldConfigFolder)) {
Files.createDirectory(oldConfigFolder);
}
Path randomStoreConfigDirectory = oldConfigFolder.resolve(RANDOM_OLD);
if (Files.notExists(randomStoreConfigDirectory)) {
Files.createDirectory(randomStoreConfigDirectory);
}
Path oldConfigPath = dataFolder.resolve(configFile);
Files.move(oldConfigPath, randomStoreConfigDirectory.resolve(configFile));
createConfigFile(dataFolder, configFile, defaultResourceID);
}
}

View File

@@ -17,7 +17,6 @@ import java.util.UUID;
*
* @author Ham1255
* @since 0.7.0
*
*/
public interface EventsPlatform {

View File

@@ -0,0 +1,24 @@
package com.imaginarycode.minecraft.redisbungee.api.payloads;
public abstract class AbstractPayload {
private final String senderProxy;
public AbstractPayload(String proxyId) {
this.senderProxy = proxyId;
}
public AbstractPayload(String senderProxy, String className) {
this.senderProxy = senderProxy;
}
public String senderProxy() {
return senderProxy;
}
public String getClassName() {
return getClass().getName();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
import java.lang.reflect.Type;
public class AbstractPayloadSerializer implements JsonSerializer<AbstractPayload>, JsonDeserializer<AbstractPayload> {
@Override
public AbstractPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
return new AbstractPayload(jsonObject.get("proxy").getAsString(), jsonObject.get("class").getAsString()) {
};
}
@Override
public JsonElement serialize(AbstractPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
return jsonObject;
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class DeathPayload extends AbstractPayload {
public DeathPayload(String proxyId) {
super(proxyId);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class HeartbeatPayload extends AbstractPayload {
public record HeartbeatData(long heartbeat, int players) {
}
private final HeartbeatData data;
public HeartbeatPayload(String proxyId, HeartbeatData data) {
super(proxyId);
this.data = data;
}
public HeartbeatData data() {
return data;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class PubSubPayload extends AbstractPayload {
private final String channel;
private final String message;
public PubSubPayload(String proxyId, String channel, String message) {
super(proxyId);
this.channel = channel;
this.message = message;
}
public String channel() {
return channel;
}
public String message() {
return message;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy;
import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload;
public class RunCommandPayload extends AbstractPayload {
private final String proxyToRun;
private final String command;
public RunCommandPayload(String proxyId, String proxyToRun, String command) {
super(proxyId);
this.proxyToRun = proxyToRun;
this.command = command;
}
public String proxyToRun() {
return proxyToRun;
}
public String command() {
return command;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload;
import java.lang.reflect.Type;
public class DeathPayloadSerializer implements JsonSerializer<DeathPayload>, JsonDeserializer<DeathPayload> {
private static final Gson gson = new Gson();
@Override
public DeathPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String senderProxy = jsonObject.get("proxy").getAsString();
return new DeathPayload(senderProxy);
}
@Override
public JsonElement serialize(DeathPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
return jsonObject;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload;
import java.lang.reflect.Type;
public class HeartbeatPayloadSerializer implements JsonSerializer<HeartbeatPayload>, JsonDeserializer<HeartbeatPayload> {
@Override
public HeartbeatPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String senderProxy = jsonObject.get("proxy").getAsString();
long heartbeat = jsonObject.get("heartbeat").getAsLong();
int players = jsonObject.get("players").getAsInt();
return new HeartbeatPayload(senderProxy, new HeartbeatPayload.HeartbeatData(heartbeat, players));
}
@Override
public JsonElement serialize(HeartbeatPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
jsonObject.add("heartbeat", new JsonPrimitive(src.data().heartbeat()));
jsonObject.add("players", new JsonPrimitive(src.data().players()));
return jsonObject;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload;
import java.lang.reflect.Type;
public class PubSubPayloadSerializer implements JsonSerializer<PubSubPayload>, JsonDeserializer<PubSubPayload> {
private static final Gson gson = new Gson();
@Override
public PubSubPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String senderProxy = jsonObject.get("proxy").getAsString();
String channel = jsonObject.get("channel").getAsString();
String message = jsonObject.get("message").getAsString();
return new PubSubPayload(senderProxy, channel, message);
}
@Override
public JsonElement serialize(PubSubPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
jsonObject.add("channel", new JsonPrimitive(src.channel()));
jsonObject.add("message", context.serialize(src.message()));
return jsonObject;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson;
import com.google.gson.*;
import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload;
import java.lang.reflect.Type;
public class RunCommandPayloadSerializer implements JsonSerializer<RunCommandPayload>, JsonDeserializer<RunCommandPayload> {
@Override
public RunCommandPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String senderProxy = jsonObject.get("proxy").getAsString();
String proxyToRun = jsonObject.get("proxy-to-run").getAsString();
String command = jsonObject.get("command").getAsString();
return new RunCommandPayload(senderProxy, proxyToRun, command);
}
@Override
public JsonElement serialize(RunCommandPayload src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("proxy", new JsonPrimitive(src.senderProxy()));
jsonObject.add("proxy-to-run", new JsonPrimitive(src.proxyToRun()));
jsonObject.add("command", context.serialize(src.command()));
return jsonObject;
}
}

View File

@@ -17,7 +17,7 @@ import java.io.IOException;
import java.time.Duration;
public class JedisClusterSummoner implements Summoner<JedisCluster> {
public final ClusterConnectionProvider clusterConnectionProvider;
private final ClusterConnectionProvider clusterConnectionProvider;
public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) {
this.clusterConnectionProvider = clusterConnectionProvider;
@@ -35,6 +35,8 @@ public class JedisClusterSummoner implements Summoner<JedisCluster> {
@Override
public JedisCluster obtainResource() {
return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(30000));
return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(10));
}
}

View File

@@ -11,9 +11,7 @@
package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import redis.clients.jedis.providers.PooledConnectionProvider;
import java.time.Duration;

View File

@@ -10,6 +10,8 @@
package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.UnifiedJedis;
import java.io.Closeable;
@@ -18,9 +20,8 @@ import java.io.Closeable;
*
* @author Ham1255
* @since 0.7.0
*
*/
public interface Summoner<P> extends Closeable {
public interface Summoner<P extends UnifiedJedis> extends Closeable {
P obtainResource();

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import redis.clients.jedis.*;
public abstract class RedisPipelineTask<T> extends RedisTask<T> {
public RedisPipelineTask(AbstractRedisBungeeAPI api) {
super(api);
}
public RedisPipelineTask(RedisBungeePlugin<?> plugin) {
super(plugin);
}
@Override
public T unifiedJedisTask(UnifiedJedis unifiedJedis) {
if (unifiedJedis instanceof JedisPooled pooled) {
try (Pipeline pipeline = pooled.pipelined()) {
return doPooledPipeline(pipeline);
}
} else if (unifiedJedis instanceof JedisCluster jedisCluster) {
try (ClusterPipeline pipeline = jedisCluster.pipelined()) {
return clusterPipeline(pipeline);
}
}
return null;
}
public abstract T doPooledPipeline(Pipeline pipeline);
public abstract T clusterPipeline(ClusterPipeline pipeline);
}

View File

@@ -11,11 +11,11 @@
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import redis.clients.jedis.UnifiedJedis;
import java.util.concurrent.Callable;
@@ -27,23 +27,22 @@ import java.util.concurrent.Callable;
public abstract class RedisTask<V> implements Runnable, Callable<V> {
protected final Summoner<?> summoner;
protected final AbstractRedisBungeeAPI api;
protected RedisBungeePlugin<?> plugin;
protected final RedisBungeeMode mode;
@Override
public V call() throws Exception {
return execute();
return this.execute();
}
public RedisTask(AbstractRedisBungeeAPI api) {
this.api = api;
this.summoner = api.getSummoner();
this.mode = api.getMode();
}
public RedisTask(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
this.api = plugin.getAbstractRedisBungeeApi();
this.summoner = api.getSummoner();
this.summoner = plugin.getSummoner();
this.mode = plugin.getRedisBungeeMode();
}
public abstract V unifiedJedisTask(UnifiedJedis unifiedJedis);
@@ -55,20 +54,14 @@ public abstract class RedisTask<V> implements Runnable, Callable<V> {
public V execute() {
// JedisCluster, JedisPooled in fact is just UnifiedJedis does not need new instance since its single instance anyway.
if (api.getMode() == RedisBungeeMode.SINGLE) {
if (mode == RedisBungeeMode.SINGLE) {
JedisPooledSummoner jedisSummoner = (JedisPooledSummoner) summoner;
return this.unifiedJedisTask(jedisSummoner.obtainResource());
} else if (api.getMode() == RedisBungeeMode.CLUSTER) {
} else if (mode == RedisBungeeMode.CLUSTER) {
JedisClusterSummoner jedisClusterSummoner = (JedisClusterSummoner) summoner;
return this.unifiedJedisTask(jedisClusterSummoner.obtainResource());
}
return null;
}
public RedisBungeePlugin<?> getPlugin() {
if (plugin == null) {
throw new NullPointerException("Plugin is null in the task");
}
return plugin;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.CachedUUIDEntry;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisException;
import java.util.ArrayList;
public class UUIDCleanupTask extends RedisTask<Void>{
private final Gson gson = new Gson();
private final RedisBungeePlugin<?> plugin;
public UUIDCleanupTask(RedisBungeePlugin<?> plugin) {
super(plugin);
this.plugin = plugin;
}
// this code is inspired from https://github.com/minecrafter/redisbungeeclean
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
final long number = unifiedJedis.hlen("uuid-cache");
plugin.logInfo("Found {} entries", number);
ArrayList<String> fieldsToRemove = new ArrayList<>();
unifiedJedis.hgetAll("uuid-cache").forEach((field, data) -> {
CachedUUIDEntry cachedUUIDEntry = gson.fromJson(data, CachedUUIDEntry.class);
if (cachedUUIDEntry.expired()) {
fieldsToRemove.add(field);
}
});
if (!fieldsToRemove.isEmpty()) {
unifiedJedis.hdel("uuid-cache", fieldsToRemove.toArray(new String[0]));
}
plugin.logInfo("deleted {} entries", fieldsToRemove.size());
} catch (JedisException e) {
plugin.logFatal("There was an error fetching information", e);
}
return null;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.util;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.UnifiedJedis;
public class InitialUtils {
public static void checkRedisVersion(RedisBungeePlugin<?> plugin) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
// This is more portable than INFO <section>
String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO));
for (String s : info.split("\r\n")) {
if (s.startsWith("redis_version:")) {
String version = s.split(":")[1];
plugin.logInfo("Redis server version: " + version);
if (!RedisUtil.isRedisVersionRight(version)) {
plugin.logFatal("Your version of Redis (" + version + ") is not at least version " + RedisUtil.MAJOR_VERSION + "." + RedisUtil.MINOR_VERSION + " RedisBungee requires a newer version of Redis.");
throw new RuntimeException("Unsupported Redis version detected");
}
long uuidCacheSize = unifiedJedis.hlen("uuid-cache");
if (uuidCacheSize > 750000) {
plugin.logInfo("Looks like you have a really big UUID cache! Run '/rb clean' to remove expired cache entries");
}
break;
}
}
return null;
}
}.execute();
}
}

View File

@@ -5,6 +5,10 @@ import com.google.common.annotations.VisibleForTesting;
@VisibleForTesting
public class RedisUtil {
public final static int PROXY_TIMEOUT = 30;
public static final int MAJOR_VERSION = 6;
public static final int MINOR_VERSION = 2;
public static boolean isRedisVersionRight(String redisVersion) {
String[] args = redisVersion.split("\\.");
if (args.length < 2) {
@@ -12,7 +16,10 @@ public class RedisUtil {
}
int major = Integer.parseInt(args[0]);
int minor = Integer.parseInt(args[1]);
return major >= 3 && minor >= 0;
if (major > MAJOR_VERSION) return true;
return major == MAJOR_VERSION && minor >= MINOR_VERSION;
}
// Ham1255: i am keeping this if some plugin uses this *IF*

View File

@@ -7,7 +7,7 @@ import com.google.common.io.ByteArrayDataOutput;
import java.util.Collection;
import java.util.Map;
public class Serializations {
public class MultiMapSerialization {
public static void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
output.writeInt(collection.elementSet().size());
@@ -36,4 +36,5 @@ public class Serializations {
output.writeUTF(o.toString());
}
}
}

View File

@@ -13,28 +13,7 @@ package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import java.util.Calendar;
import java.util.UUID;
public class CachedUUIDEntry {
private final String name;
private final UUID uuid;
private final Calendar expiry;
public CachedUUIDEntry(String name, UUID uuid, Calendar expiry) {
this.name = name;
this.uuid = uuid;
this.expiry = expiry;
}
public String getName() {
return name;
}
public UUID getUuid() {
return uuid;
}
public Calendar getExpiry() {
return expiry;
}
public record CachedUUIDEntry(String name, UUID uuid, Calendar expiry) {
public boolean expired() {
return Calendar.getInstance().after(expiry);

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class NameFetcher {
private static final OkHttpClient httpClient = new OkHttpClient();
private static final Gson gson = new Gson();
public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException {
String name = getName(uuid);
if (name == null) return Collections.emptyList();
return Collections.singletonList(name);
}
public static String getName(UUID uuid) throws IOException {
String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString();
Request request = new Request.Builder()
.addHeader("User-Agent", "RedisBungee-ProxioDev")
.url(url)
.get()
.build();
ResponseBody body = httpClient.newCall(request).execute().body();
String response = body.string();
body.close();
JsonObject json = gson.fromJson(response, JsonObject.class);
if (!json.has("success") || !json.get("success").getAsBoolean()) return null;
if (!json.has("data")) return null;
JsonObject data = json.getAsJsonObject("data");
if (!data.has("player")) return null;
JsonObject player = data.getAsJsonObject("player");
if (!player.has("username")) return null;
return player.get("username").getAsString();
}
}

View File

@@ -12,7 +12,7 @@ package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.squareup.okhttp.*;
import okhttp3.*;
import java.util.HashMap;
import java.util.List;
@@ -28,13 +28,7 @@ public class UUIDFetcher implements Callable<Map<String, UUID>> {
private final List<String> names;
private final boolean rateLimiting;
private static final Gson gson = new Gson();
public static void setHttpClient(OkHttpClient httpClient) {
UUIDFetcher.httpClient = httpClient;
}
private static OkHttpClient httpClient;
private static final OkHttpClient httpClient = new OkHttpClient();
private UUIDFetcher(List<String> names, boolean rateLimiting) {
this.names = ImmutableList.copyOf(names);

View File

@@ -12,16 +12,17 @@ package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import org.checkerframework.checker.nullness.qual.NonNull;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisException;
import java.util.*;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
@@ -60,7 +61,7 @@ public final class UUIDTranslator {
CachedUUIDEntry cachedUUIDEntry = nameToUuidMap.get(player.toLowerCase());
if (cachedUUIDEntry != null) {
if (!cachedUUIDEntry.expired())
return cachedUUIDEntry.getUuid();
return cachedUUIDEntry.uuid();
else
nameToUuidMap.remove(player);
}
@@ -92,11 +93,11 @@ public final class UUIDTranslator {
if (entry.expired()) {
unifiedJedis.hdel("uuid-cache", player.toLowerCase());
// Doesn't hurt to also remove the UUID entry as well.
unifiedJedis.hdel("uuid-cache", entry.getUuid().toString());
unifiedJedis.hdel("uuid-cache", entry.uuid().toString());
} else {
nameToUuidMap.put(player.toLowerCase(), entry);
uuidToNameMap.put(entry.getUuid(), entry);
return entry.getUuid();
uuidToNameMap.put(entry.uuid(), entry);
return entry.uuid();
}
}
@@ -140,7 +141,7 @@ public final class UUIDTranslator {
CachedUUIDEntry cachedUUIDEntry = uuidToNameMap.get(player);
if (cachedUUIDEntry != null) {
if (!cachedUUIDEntry.expired())
return cachedUUIDEntry.getName();
return cachedUUIDEntry.name();
else
uuidToNameMap.remove(player);
}
@@ -158,28 +159,23 @@ public final class UUIDTranslator {
unifiedJedis.hdel("uuid-cache", player.toString());
// Doesn't hurt to also remove the named entry as well.
// TODO: Since UUIDs are fixed, we could look up the name and see if the UUID matches.
unifiedJedis.hdel("uuid-cache", entry.getName());
unifiedJedis.hdel("uuid-cache", entry.name());
} else {
nameToUuidMap.put(entry.getName().toLowerCase(), entry);
nameToUuidMap.put(entry.name().toLowerCase(), entry);
uuidToNameMap.put(player, entry);
return entry.getName();
return entry.name();
}
}
if (!expensiveLookups || !plugin.isOnlineMode())
return null;
// That didn't work. Let's ask Mojang. This call may fail, because Mojang is insane.
//
// UPDATE: Mojang has removed the API somewhere in september/2022 due privacy issues
// this is expected to fail now, so we will keep logging it until we figure out something or remove name fetching completely
// Name fetching class was deprecated as result
// That didn't work. Let's ask PlayerDB.
String name;
try {
plugin.logFatal("Due Mojang removing the naming API, we were unable to fetch player names.");
name = Iterables.getLast(NameFetcher.nameHistoryFromUuid(player));
name = NameFetcher.getName(player);
} catch (Exception e) {
plugin.logFatal("Unable to fetch name from Mojang for " + player);
plugin.logFatal("Unable to fetch name from PlayerDB for " + player);
return null;
}

View File

@@ -0,0 +1,203 @@
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation
distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates'
from a Contributor if it was added to the Program by such Contributor
itself or anyone acting on such Contributor's behalf. Contributions do not
include additions to the Program which: (i) are separate modules of
software distributed in conjunction with the Program under their own
license agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly
perform, distribute and sublicense the Contribution of such Contributor,
if any, and such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of
the Contribution and the Program if, at the time the Contribution is
added by the Contributor, such addition of the Contribution causes such
combination to be covered by the Licensed Patents. The patent license
shall not apply to any other combinations which include the Contribution.
No hardware per se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses
to its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor
disclaims any liability to Recipient for claims brought by any other
entity based on infringement of intellectual property rights or
otherwise. As a condition to exercising the rights and licenses granted
hereunder, each Recipient hereby assumes sole responsibility to secure
any other intellectual property rights needed, if any. For example, if a
third party patent license is required to allow Recipient to distribute
the Program, it is Recipient's responsibility to acquire that license
before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright
license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under
its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties
and conditions, express and implied, including warranties or
conditions of title and non-infringement, and implied warranties or
conditions of merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and
consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are
offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained
within the Program.
Each Contributor must identify itself as the originator of its Contribution,
if
any, in a manner that reasonably allows subsequent Recipients to identify the
originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a manner
which does not create potential liability for other Contributors. Therefore,
if a Contributor includes the Program in a commercial product offering, such
Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
every other Contributor ("Indemnified Contributor") against any losses,
damages and costs (collectively "Losses") arising from claims, lawsuits and
other legal actions brought by a third party against the Indemnified
Contributor to the extent caused by the acts or omissions of such Commercial
Contributor in connection with its distribution of the Program in a commercial
product offering. The obligations in this section do not apply to any claims
or Losses relating to any actual or alleged intellectual property
infringement. In order to qualify, an Indemnified Contributor must:
a) promptly notify the Commercial Contributor in writing of such claim, and
b) allow the Commercial Contributor to control, and cooperate with the
Commercial Contributor in, the defense and any related settlement
negotiations. The Indemnified Contributor may participate in any such claim at
its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If
that Commercial Contributor then makes performance claims, or offers
warranties related to Product X, those performance claims and warranties are
such Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a
court requires any other Contributor to pay any damages as a result, the
Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
Recipient is solely responsible for determining the appropriateness of using
and distributing the Program and assumes all risks associated with its
exercise of rights under this Agreement , including but not limited to the
risks and costs of program errors, compliance with applicable laws, damage to
or loss of data, programs or equipment, and unavailability or interruption of
operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of the
remainder of the terms of this Agreement, and without further action by the
parties hereto, such provision shall be reformed to the minimum extent
necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted
under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and does
not cure such failure in a reasonable period of time after becoming aware of
such noncompliance. If all Recipient's rights under this Agreement terminate,
Recipient agrees to cease use and distribution of the Program as soon as
reasonably practicable. However, Recipient's obligations under this Agreement
and any licenses granted by Recipient relating to the Program shall continue
and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to
time. No one other than the Agreement Steward has the right to modify this
Agreement. The Eclipse Foundation is the initial Agreement Steward. The
Eclipse Foundation may assign the responsibility to serve as the Agreement
Steward to a suitable separate entity. Each new version of the Agreement will
be given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version of the
Agreement is published, Contributor may elect to distribute the Program
(including its Contributions) under the new version. Except as expressly
stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
licenses to the intellectual property of any Contributor under this Agreement,
whether expressly, by implication, estoppel or otherwise. All rights in the
Program not expressly granted under this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial in
any resulting litigation.

View File

@@ -0,0 +1,151 @@
# RedisBungee configuration file.
# Notice:
# Redis 7.2.4 is last free and open source Redis version after license change
# https://download.redis.io/releases/redis-7.2.4.tar.gz which you have to compile yourself,
# unless your package manager still provide it.
# Here is The alternatives
# - 'ValKey' By linux foundation https://valkey.io/download/
# - 'KeyDB' by Snapchat inc https://docs.keydb.dev/docs/download/
# The 'Redis', 'ValKey', 'KeyDB' server you will use.
# these settings are ignored when cluster mode is enabled.
redis-server: 127.0.0.1
redis-port: 6379
# Cluster Mode
# enabling this option will enable cluster mode.
cluster-mode-enabled: false
# FORMAT:
# redis-cluster-servers:
# - host: 127.0.0.1`
# port: 2020
# - host: 127.0.0.1
# port: 2021
# - host: 127.0.0.1
# port: 2021
# you can set single server and Jedis will automatically discover cluster nodes,
# but might fail if this single redis node is down when Proxy startup, its recommended put the all the nodes
redis-cluster-servers:
- host: 127.0.0.1
port: 6379
# OPTIONAL: if your redis uses acl usernames set the username here. leave empty for no username.
redis-username: ""
# OPTIONAL but recommended: If your Redis server uses AUTH, set the required password.
redis-password: ""
# Maximum connections that will be maintained to the Redis server.
# The default is 10. This setting should be left as-is unless you have some wildly
# inefficient plugins or a lot of players.
max-redis-connections: 10
# since redis can support ssl by version 6 you can use SSL/TLS in redis bungee too!
# but there is more configuration needed to work see https://github.com/ProxioDev/RedisBungee/issues/18
# Keep note that SSL/TLS connections will decrease redis performance so use it when needed.
useSSL: false
# An identifier for this network, which helps to separate redisbungee instances on same redis instance.
# You can use environment variable 'REDISBUNGEE_NETWORK_ID' to override
# Depending on the platform bungeecord or velocity system will append platform id to the network id
# to prevent proxies from different platforms from seeing each other. since 0.13.0
network-id: "main"
# An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank.
# You can set Environment variable 'REDISBUNGEE_PROXY_ID' to override
proxy-id: "proxy-1"
# since RedisBungee Internally now uses UnifiedJedis instead of Jedis, JedisPool.
# which will break compatibility with old plugins that uses RedisBungee JedisPool
# so to mitigate this issue, RedisBungee will create an JedisPool for compatibility reasons.
# disabled by default
# Automatically disabled when cluster mode is enabled
enable-jedis-pool-compatibility: false
# max connections for the compatibility pool
compatibility-max-connections: 3
# restore old login behavior before 0.9.0 update
# enabled by default
# when true: when player login and there is old player with same uuid it will get disconnected as result and new player will log in
# when false: when a player login but login will fail because old player is still connected.
kick-when-online: true
# enabled by default
# this option tells RedisBungee handle motd and set online count, when motd is requested
# you can disable this when you want to handle motd yourself, use RedisBungee api to get total players when needed :)
handle-motd: true
# MOTD plugins compatibility setting
# Allowed values: FIRST, NORMAL, LAST
# This option enables RedisBungee to manage various interactions between other plugins and the online player count,
# which is dynamically updated to a global player count in ping responses if the handle-motd option is enabled.
# If you encounter issues with other plugins accessing or modifying the player count, try using a value of FIRST or LAST.
handle-motd-order: NORMAL
# A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic
# restart scripts.
# Automatically disabled if handle-motd is disabled.
exempt-ip-addresses: []
# disabled by default
# RedisBungee will attempt to connect player to last server that was stored.
reconnect-to-last-server: false
# For redis bungee legacy commands
# either can be run using '/rbl glist' for example
# or if 'install' is set to true '/glist' can be used.
# 'install' also overrides the proxy installed commands
#
# In legacy commands each command got it own permissions since they had it own permission pre new command system,
# so it's also applied to subcommands in '/rbl'.
commands:
# Permission redisbungee.legacy.use
redisbungee-legacy:
enabled: false
subcommands:
# Permission redisbungee.command.glist
glist:
enabled: false
install: false
# Permission redisbungee.command.find
find:
enabled: false
install: false
# Permission redisbungee.command.lastseen
lastseen:
enabled: false
install: false
# Permission redisbungee.command.ip
ip:
enabled: false
install: false
# Permission redisbungee.command.pproxy
pproxy:
enabled: false
install: false
# Permission redisbungee.command.sendtoall
sendtoall:
enabled: false
install: false
# Permission redisbungee.command.serverid
serverid:
enabled: false
install: false
# Permission redisbungee.command.serverids
serverids:
enabled: false
install: false
# Permission redisbungee.command.plist
plist:
enabled: false
install: false
# Permission redisbungee.command.use
redisbungee:
enabled: true
# Config version DO NOT CHANGE!!!!
config-version: 2

26
commands/build.gradle.kts Normal file
View File

@@ -0,0 +1,26 @@
plugins {
`java-library`
}
dependencies {
compileOnly(project(":RedisBungee-API"))
implementation(libs.acf.core)
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.miniMessage)
}
description = "RedisBungee common commands"
tasks {
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(17)
}
javadoc {
options.encoding = Charsets.UTF_8.name()
}
processResources {
filteringCharset = Charsets.UTF_8.name()
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands;
import co.aikar.commands.CommandContexts;
import co.aikar.commands.CommandManager;
import co.aikar.commands.InvalidCommandArgument;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.legacy.LegacyRedisBungeeCommands;
import java.util.UUID;
public class CommandLoader {
public static void initCommands(CommandManager<?, ?, ?, ?, ?, ?> commandManager, RedisBungeePlugin<?> plugin) {
registerContexts(commandManager);
var commandsConfiguration = plugin.configuration().commandsConfiguration();
if (commandsConfiguration.redisbungeeEnabled()) {
commandManager.registerCommand(new CommandRedisBungee(plugin));
}
if (commandsConfiguration.redisbungeeLegacyEnabled()) {
commandManager.registerCommand(new LegacyRedisBungeeCommands(commandManager,plugin));
}
commandManager.registerCommand(new CommandRedisBungeeDebug(plugin));
}
private static void registerContexts(CommandManager<?, ?, ?, ?, ?, ?> commandManager) {
CommandContexts<?> commandContexts = commandManager.getCommandContexts();
commandContexts.registerContext(UUID.class, c -> {
String uuidString = c.popFirstArg();
try {
return UUID.fromString(uuidString);
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("invaild uuid");
}
});
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.RegisteredCommand;
import co.aikar.commands.annotation.*;
import com.google.common.primitives.Ints;
import com.imaginarycode.minecraft.redisbungee.Constants;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
import com.imaginarycode.minecraft.redisbungee.commands.utils.StopperUUIDCleanupTask;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@CommandAlias("rb|redisbungee")
@CommandPermission("redisbungee.command.use")
@Description("Main command")
public class CommandRedisBungee extends AdventureBaseCommand {
private final RedisBungeePlugin<?> plugin;
public CommandRedisBungee(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
@Default
@Subcommand("info|version|git")
@Description("information about current redisbungee build")
public void info(CommandIssuer issuer) {
final String message = """
<color:aqua>This proxy is running RedisBungee Limework's fork
<color:gold>========================================
<color:aqua>RedisBungee version: <color:green><version>
<color:aqua>Commit: <color:green><commit>
<color:gold>========================================
<color:gold>run /rb help for more commands""";
sendMessage(
issuer,
MiniMessage.miniMessage()
.deserialize(
message,
Placeholder.component("version", Component.text(Constants.VERSION)),
Placeholder.component(
"commit",
Component.text(Constants.GIT_COMMIT.substring(0, 8))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, Constants.getGithubCommitLink()))
.hoverEvent(HoverEvent.showText(Component.text("Click me to open: " + Constants.getGithubCommitLink())))
)));
}
// <color:aqua>......: <color:green>......
@HelpCommand
@Description("shows the help page")
public void help(CommandIssuer issuer) {
final String barFormat = "<color:gold>========================================";
final String commandFormat = "<color:aqua>/rb <sub-command>: <color:green><description>";
TextComponent.Builder message = Component.text();
message.append(MiniMessage.miniMessage().deserialize(barFormat));
getSubCommands().forEach((subCommand, registeredCommand) -> {
String[] split = registeredCommand.getCommand().split(" ");
if (split.length > 1 && subCommand.equalsIgnoreCase(split[1])) {
message.appendNewline().append(MiniMessage.miniMessage().deserialize(commandFormat, Placeholder.component("sub-command", Component.text(subCommand)),
Placeholder.component("description", MiniMessage.miniMessage().deserialize(registeredCommand.getHelpText()))
));
}
});
message.appendNewline().append(MiniMessage.miniMessage().deserialize(barFormat));
sendMessage(issuer, message.build());
}
@Subcommand("clean")
@Description("cleans up the uuid cache<color:red> <bold>WARNING...</bold> <color:white>command above could cause performance issues")
@Private
public void cleanUp(CommandIssuer issuer) {
if (StopperUUIDCleanupTask.isRunning) {
sendMessage(issuer,
Component.text("cleanup is currently running!").color(NamedTextColor.RED));
return;
}
sendMessage(issuer,
Component.text("cleanup is Starting, you should see the output status in the proxy console").color(NamedTextColor.GOLD));
plugin.executeAsync(new StopperUUIDCleanupTask(plugin));
}
private List<Map.Entry<String, Integer>> subListProxies(List<Map.Entry<String, Integer>> data, final int currentPage, final int pageSize) {
return data.subList(((currentPage * pageSize) - pageSize), Ints.constrainToRange(currentPage * pageSize, 0, data.size()));
}
@Subcommand("show")
@Description("Shows proxies in this network")
public void showProxies(CommandIssuer issuer, String[] args) {
final String closer = "<color:gold>========================================";
final String pageTop = "<color:yellow>Page: <color:green><current>/<max> <color:yellow>Network ID: <color:green><network> <color:yellow>Proxies online: <color:green><proxies>";
final String proxy = "<color:yellow><proxy><here> : <color:green><players> online";
final String proxyHere = " (#) ";
final String nextPage = ">>>>>";
final String previousPage = "<<<<< ";
final String pageInvalid = "<color:red>invalid page";
final String noProxies = "<color:red>No proxies were found :(";
final int pageSize = 16;
int currentPage;
if (args.length > 0) {
try {
currentPage = Integer.parseInt(args[0]);
if (currentPage < 1) currentPage = 1;
} catch (NumberFormatException e) {
sendMessage(issuer, MiniMessage.miniMessage().deserialize(pageInvalid));
return;
}
} else currentPage = 1;
var data = new ArrayList<>(plugin.proxyDataManager().eachProxyCount().entrySet());
// there is no way this runs because there is always an heartbeat.
// if not could be some shenanigans done by devs :P
if (data.isEmpty()) {
sendMessage(issuer, MiniMessage.miniMessage().deserialize(noProxies));
return;
}
// compute the total pages
int maxPages = (int) Math.ceil(data.size() / (double) pageSize);
if (currentPage > maxPages) currentPage = maxPages;
var subList = subListProxies(data, currentPage, pageSize);
TextComponent.Builder builder = Component.text();
builder.append(MiniMessage.miniMessage().deserialize(closer)).appendNewline();
builder.append(MiniMessage.miniMessage().deserialize(pageTop,
Placeholder.component("current", Component.text(currentPage)),
Placeholder.component("max", Component.text(maxPages)),
Placeholder.component("network", Component.text(plugin.proxyDataManager().networkId())),
Placeholder.component("proxies", Component.text(data.size()))
)).appendNewline();
int left = pageSize;
for (Map.Entry<String, Integer> entrySet : subList) {
builder.append(MiniMessage.miniMessage().deserialize(proxy,
Placeholder.component("proxy", Component.text(entrySet.getKey())),
Placeholder.component("here", Component.text(plugin.proxyDataManager().proxyId().equals(entrySet.getKey()) ? proxyHere : "")),
Placeholder.component("players", Component.text(entrySet.getValue()))
)).appendNewline();
left--;
}
while(left > 0) {
builder.appendNewline();
left--;
}
if (currentPage > 1) {
builder.append(MiniMessage.miniMessage().deserialize(previousPage)
.color(NamedTextColor.WHITE).clickEvent(ClickEvent.runCommand("/rb show " + (currentPage - 1))));
} else {
builder.append(MiniMessage.miniMessage().deserialize(previousPage).color(NamedTextColor.GRAY));
}
if (subList.size() == pageSize && !subListProxies(data, currentPage + 1, pageSize).isEmpty()) {
builder.append(MiniMessage.miniMessage().deserialize(nextPage)
.color(NamedTextColor.WHITE).clickEvent(ClickEvent.runCommand("/rb show " + (currentPage + 1))));
} else {
builder.append(MiniMessage.miniMessage().deserialize(nextPage).color(NamedTextColor.GRAY));
}
builder.appendNewline();
builder.append(MiniMessage.miniMessage().deserialize(closer));
sendMessage(issuer, builder.build());
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.*;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
import java.util.UUID;
@CommandAlias("rbd|redisbungeedebug")
@CommandPermission("redisbungee.command.debug.use")
@Description("debug commands")
public class CommandRedisBungeeDebug extends AdventureBaseCommand {
private final RedisBungeePlugin<?> plugin;
public CommandRedisBungeeDebug(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
@Subcommand("kickByName")
@Description("kicks a player from the network by name")
@Private
public void kick(CommandIssuer issuer, String playerName) {
plugin.playerDataManager().serializedPlayerKick(plugin.getUuidTranslator().getTranslatedUuid(playerName, false), "kicked using redisbungee api using name");
}
@Subcommand("kickByUUID")
@Description("kicks a player from the network by UUID")
@Private
public void kick(CommandIssuer issuer, UUID uuid) {
plugin.playerDataManager().serializedPlayerKick(uuid, "kicked using redisbungee api using uuid");
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("find|rfind")
@CommandPermission("redisbungee.command.find")
public class CommandFind extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandFind(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void find(CommandIssuer issuer, String[] args) {
rootCommand.find(issuer, args);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("glist|rglist")
@CommandPermission("redisbungee.command.glist")
public class CommandGList extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandGList(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void gList(CommandIssuer issuer, String[] args) {
rootCommand.gList(issuer, args);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("ip|playerip|rip|rplayerip")
@CommandPermission("redisbungee.command.ip")
public class CommandIp extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandIp(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void ip(CommandIssuer issuer, String[] args) {
this.rootCommand.ip(issuer, args);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("lastseen|rlastseend")
@CommandPermission("redisbungee.command.lastseen")
public class CommandLastSeen extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandLastSeen(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void lastSeen(CommandIssuer issuer, String[] args) {
this.rootCommand.lastSeen(issuer,args);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("pproxy")
@CommandPermission("redisbungee.command.pproxy")
public class CommandPProxy extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandPProxy(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void playerProxy(CommandIssuer issuer, String[] args) {
this.rootCommand.playerProxy(issuer,args);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("plist|rplist")
@CommandPermission("redisbungee.command.plist")
public class CommandPlist extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandPlist(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void playerList(CommandIssuer issuer, String[] args) {
this.rootCommand.playerList(issuer, args);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("sendtoall|rsendtoall")
@CommandPermission("redisbungee.command.sendtoall")
public class CommandSendToAll extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandSendToAll(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void sendToAll(CommandIssuer issuer, String[] args) {
this.rootCommand.sendToAll(issuer, args);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("serverid|rserverid")
@CommandPermission("redisbungee.command.serverid")
public class CommandServerId extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandServerId(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void serverId(CommandIssuer issuer) {
this.rootCommand.serverId(issuer);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
@CommandAlias("serverids|rserverids")
@CommandPermission("redisbungee.command.serverids")
public class CommandServerIds extends AdventureBaseCommand {
private final LegacyRedisBungeeCommands rootCommand;
public CommandServerIds(LegacyRedisBungeeCommands rootCommand) {
this.rootCommand = rootCommand;
}
@Default
public void serverIds(CommandIssuer issuer) {
this.rootCommand.serverIds(issuer);
}
}

View File

@@ -0,0 +1,260 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.legacy;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.CommandManager;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Subcommand;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
@CommandAlias("rbl|redisbungeelegacy")
@CommandPermission("redisbungee.legacy.use")
public class LegacyRedisBungeeCommands extends AdventureBaseCommand {
private final RedisBungeePlugin<?> plugin;
public LegacyRedisBungeeCommands(CommandManager<?, ?, ?, ?, ?, ?> commandManager, RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
var commands = plugin.configuration().commandsConfiguration().legacySubCommandsConfiguration();
if (!plugin.configuration().commandsConfiguration().redisbungeeLegacyEnabled()) throw new IllegalStateException("someone tried to init me while disabled!");
if (commands == null) throw new NullPointerException("commands config is null!!");
if (commands.installGlist()) commandManager.registerCommand(new CommandGList(this));
if (commands.installFind()) commandManager.registerCommand(new CommandFind(this));
if (commands.installIp()) commandManager.registerCommand(new CommandIp(this));
if (commands.installLastseen()) commandManager.registerCommand(new CommandLastSeen(this));
if (commands.installPlist()) commandManager.registerCommand(new CommandPlist(this));
if (commands.installPproxy()) commandManager.registerCommand(new CommandPProxy(this));
if (commands.installSendtoall()) commandManager.registerCommand(new CommandSendToAll(this));
if (commands.installServerid()) commandManager.registerCommand(new CommandServerId(this));
if (commands.installServerids()) commandManager.registerCommand(new CommandServerIds(this));
}
private static final Component NO_PLAYER_SPECIFIED =
Component.text("You must specify a player name.", NamedTextColor.RED);
private static final Component PLAYER_NOT_FOUND =
Component.text("No such player found.", NamedTextColor.RED);
private static final Component NO_COMMAND_SPECIFIED =
Component.text("You must specify a command to be run.", NamedTextColor.RED);
private static String playerPlural(int num) {
return num == 1 ? num + " player is" : num + " players are";
}
@Subcommand("glist")
@CommandPermission("redisbungee.command.glist")
public void gList(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> {
int count = plugin.getAbstractRedisBungeeApi().getPlayerCount();
Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW);
if (args.length > 0 && args[0].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
serverToPlayers.forEach((key, value) -> {
// if for any reason UUID translation fails just return the uuid as name, to make command finish executing.
String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false);
human.put(key, playerName != null ? playerName : value.toString());
});
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN);
Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW);
Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers));
}
sendMessage(issuer, playersOnline);
} else {
sendMessage(issuer, playersOnline);
sendMessage(issuer, Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW));
}
});
}
@Subcommand("find")
@CommandPermission("redisbungee.command.find")
public void find(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sendMessage(issuer, PLAYER_NOT_FOUND);
return;
}
String proxyId = plugin.playerDataManager().getProxyFor(uuid);
if (proxyId != null) {
String serverId = plugin.playerDataManager().getServerFor(uuid);
Component message = Component.text(args[0] + " is on proxy " + proxyId + " on server " + serverId +".", NamedTextColor.BLUE);
sendMessage(issuer, message);
} else {
sendMessage(issuer, PLAYER_NOT_FOUND);
}
} else {
sendMessage(issuer, NO_PLAYER_SPECIFIED);
}
});
}
@Subcommand("lastseen")
@CommandPermission("redisbungee.command.lastseen")
public void lastSeen(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sendMessage(issuer, PLAYER_NOT_FOUND);
return;
}
long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid);
TextComponent.Builder message = Component.text();
if (secs == 0) {
message.color(NamedTextColor.GREEN);
message.content(args[0] + " is currently online.");
} else if (secs != -1) {
message.color(NamedTextColor.BLUE);
message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
} else {
message.color(NamedTextColor.RED);
message.content(args[0] + " has never been online.");
}
sendMessage(issuer, message.build());
} else {
sendMessage(issuer, NO_PLAYER_SPECIFIED);
}
});
}
@Subcommand("ip")
@CommandPermission("redisbungee.command.ip")
public void ip(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sendMessage(issuer, PLAYER_NOT_FOUND);
return;
}
InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid);
if (ia != null) {
TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN);
sendMessage(issuer, message);
} else {
sendMessage(issuer, PLAYER_NOT_FOUND);
}
} else {
sendMessage(issuer, NO_PLAYER_SPECIFIED);
}
});
}
@Subcommand("pproxy")
@CommandPermission("redisbungee.command.pproxy")
public void playerProxy(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sendMessage(issuer, PLAYER_NOT_FOUND);
return;
}
String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid);
if (proxy != null) {
TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN);
sendMessage(issuer, message);
} else {
sendMessage(issuer, PLAYER_NOT_FOUND);
}
} else {
sendMessage(issuer, NO_PLAYER_SPECIFIED);
}
});
}
@Subcommand("sendtoall")
@CommandPermission("redisbungee.command.sendtoall")
public void sendToAll(CommandIssuer issuer, String[] args) {
if (args.length > 0) {
String command = Joiner.on(" ").skipNulls().join(args);
plugin.getAbstractRedisBungeeApi().sendProxyCommand(command);
TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN);
sendMessage(issuer, message);
} else {
sendMessage(issuer, NO_COMMAND_SPECIFIED);
}
}
@Subcommand("serverid")
@CommandPermission("redisbungee.command.serverid")
public void serverId(CommandIssuer issuer) {
sendMessage(issuer, Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW));
}
@Subcommand("serverids")
@CommandPermission("redisbungee.command.serverids")
public void serverIds(CommandIssuer issuer) {
sendMessage(issuer, Component.text("All Proxies IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW));
}
@Subcommand("plist")
@CommandPermission("redisbungee.command.plist")
public void playerList(CommandIssuer issuer, String[] args) {
plugin.executeAsync(() -> {
String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId();
if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) {
sendMessage(issuer, Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED));
return;
}
Set<UUID> players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy);
Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW);
if (args.length >= 2 && args[1].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
serverToPlayers.forEach((key, value) -> {
if (players.contains(value)) {
human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false));
}
});
for (String server : new TreeSet<>(human.keySet())) {
TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED);
TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW);
TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers));
}
sendMessage(issuer, playersOnline);
} else {
sendMessage(issuer, playersOnline);
sendMessage(issuer, Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW));
}
});
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.utils;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.CommandIssuer;
import net.kyori.adventure.text.Component;
/**
* this just dumb class that wraps the adventure stuff into base command
*/
public abstract class AdventureBaseCommand extends BaseCommand {
protected void sendMessage(CommandIssuer issuer, Component component) {
CommandPlatformHelper.getPlatformHelper().sendMessage(issuer, component);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands.utils;
import co.aikar.commands.CommandIssuer;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import net.kyori.adventure.text.Component;
public abstract class CommandPlatformHelper {
private static CommandPlatformHelper SINGLETON;
public abstract void sendMessage(CommandIssuer issuer, Component component);
public static void init(CommandPlatformHelper platformHelper) {
if (SINGLETON != null) {
throw new IllegalStateException("tried to re init Platform Helper");
}
SINGLETON = platformHelper;
}
public static CommandPlatformHelper getPlatformHelper() {
return SINGLETON;
}
}

View File

@@ -0,0 +1,25 @@
package com.imaginarycode.minecraft.redisbungee.commands.utils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.UUIDCleanupTask;
import redis.clients.jedis.UnifiedJedis;
public class StopperUUIDCleanupTask extends UUIDCleanupTask {
public static boolean isRunning = false;
public StopperUUIDCleanupTask(RedisBungeePlugin<?> plugin) {
super(plugin);
}
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
isRunning = true;
try {
super.unifiedJedisTask(unifiedJedis);
} catch (Exception ignored) {}
isRunning = false;
return null;
}
}

View File

View File

@@ -1,2 +1,2 @@
group=com.imaginarycode.minecraft
version = 0.11.1-SNAPSHOT
version=0.13.0-SNAPSHOT

36
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,36 @@
[versions]
guava = "33.5.0-jre"
jedis = "5.2.0"
okhttp = "4.12.0"
configurateV3 = "3.7.3"
caffeine = "3.2.3"
adventure = "4.26.1"
adventure-bungeecord-platform = "4.4.1"
acf = "e2005dd62d"
bungeecordApi = "1.21-R0.5-SNAPSHOT"
velocity = "3.5.0-SNAPSHOT"
[plugins]
blossom = { id = "net.kyori.blossom", version = "2.2.0" }
indragit = {id = "net.kyori.indra.git", version="4.0.0"}
shadow = { id = "com.gradleup.shadow", version = "9.3.1" }
run-velocity = { id = "xyz.jpenilla.run-velocity", version = "2.3.1" }
[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
jedis = { module = "redis.clients:jedis", version.ref = "jedis" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
configurateV3 = { module = "org.spongepowered:configurate-yaml", version.ref = "configurateV3" }
caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" }
adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure" }
adventure-miniMessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" }
acf-core = { module = "com.github.ProxioDev.commands:acf-core", version.ref = "acf" }
acf-bungeecord = { module = "com.github.ProxioDev.commands:acf-bungee", version.ref = "acf" }
acf-velocity = { module = "com.github.ProxioDev.commands:acf-velocity", version.ref = "acf" }
platform-bungeecord = { module = "net.md-5:bungeecord-api", version.ref = "bungeecordApi" }
adventure-platforms-bungeecord = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-bungeecord-platform" }
platform-velocity = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" }

Binary file not shown.

View File

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

Some files were not shown because too many files have changed in this diff Show More