new data system

This commit is contained in:
mohammed jasem alaajel 2024-04-12 22:37:02 +04:00
parent 006066f66c
commit 8aaae6702e
57 changed files with 1967 additions and 2116 deletions

View File

@ -1,15 +1,19 @@
# 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*.
*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/)
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
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)
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)
## Downloads
## Downloads
[![](https://raw.githubusercontent.com/Prospector/badges/master/modrinth-badge-72h-padded.png)](https://modrinth.com/plugin/redisbungee)
or from github releases
@ -17,12 +21,17 @@ or from github releases
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.
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 | ✖ |
@ -33,7 +42,6 @@ SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.8
| 6.x.x | ✔ |
| 7.x.x | ✔ |
## 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.
@ -41,7 +49,9 @@ 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>
@ -50,7 +60,9 @@ By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://j
</repository>
</repositories>
```
## gradle (kotlin dsl)
```kotlin
repositories {
maven("https://jitpack.io/")
@ -58,8 +70,11 @@ repositories {
```
# [BungeeCord](https://github.com/SpigotMC/BungeeCord)
add this in your project dependencies
add this in your project dependencies
## maven
```xml
<dependency>
<groupId>com.github.proxiodev.redisbungee</groupId>
@ -70,7 +85,9 @@ add this in your project dependencies
</dependency>
```
## gradle (kotlin dsl)
```
implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0")
@ -79,7 +96,9 @@ 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
@ -88,9 +107,10 @@ author: idk
depends: [ RedisBungee ]
```
## [Velocity](https://github.com/PaperMC/Velocity)
## maven
```xml
<dependency>
<groupId>com.github.proxiodev.redisbungee</groupId>
@ -100,7 +120,9 @@ depends: [ RedisBungee ]
<!-- <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")
@ -110,7 +132,9 @@ implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0:all
}
```
then to make your plugin depends on RedisBungee, make sure your plugin class Annotation have `@Dependency(id = "redisbungee")` like this
then to make your plugin depends on RedisBungee, make sure your plugin class Annotation
have `@Dependency(id = "redisbungee")` like this
```java
@Plugin(
id = "myplugin",
@ -124,15 +148,20 @@ 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>
@ -142,6 +171,7 @@ then use any of these in your project.
<!-- <classifier>all</classifier> USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOCATION -->
</dependency>
```
```xml
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
@ -151,6 +181,7 @@ then use any of these in your project.
<!-- <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/
@ -159,20 +190,25 @@ then use any of these in your project.
## 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.
**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:
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
* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than
Jedis
## Support
@ -184,12 +220,15 @@ 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 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

@ -22,7 +22,7 @@ dependencies {
api("redis.clients:jedis:$jedisVersion")
api("com.squareup.okhttp:okhttp:2.7.5")
api("org.spongepowered:configurate-yaml:$configurateVersion")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
// tests
testImplementation("junit:junit:4.13.2")
}
@ -68,11 +68,10 @@ tasks {
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,8 +10,6 @@
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;
@ -40,21 +38,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 +53,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 +64,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 +76,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 +87,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 +108,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 +128,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 +153,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 +164,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 +175,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 +188,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,7 +212,7 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.8.0
*/
public final String getProxyId() {
return plugin.getConfiguration().getProxyId();
return plugin.proxyDataManager().proxyId();
}
/**
@ -245,7 +236,7 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.8.0
*/
public final List<String> getAllProxies() {
return plugin.getProxiesIds();
return plugin.proxyDataManager().proxiesIds();
}
/**
@ -266,9 +257,10 @@ public abstract class AbstractRedisBungeeAPI {
*
* @param channels the channels to register
* @since 0.3
* @deprecated No longer required
*/
@Deprecated
public final void registerPubSubChannels(String... channels) {
plugin.getPubSubListener().addChannel(channels);
}
/**
@ -276,13 +268,10 @@ public abstract class AbstractRedisBungeeAPI {
*
* @param channels the channels to unregister
* @since 0.3
* @deprecated No longer required
*/
@Deprecated
public final void unregisterPubSubChannels(String... channels) {
for (String channel : channels) {
Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel");
}
plugin.getPubSubListener().removeChannel(channels);
}
/**
@ -355,6 +344,7 @@ public abstract class AbstractRedisBungeeAPI {
/**
* Kicks a player from the network
* calls {@link #getUuidFromName(String)} to get uuid
*
* @param playerName player name
* @param message kick message that player will see on kick
@ -362,7 +352,7 @@ public abstract class AbstractRedisBungeeAPI {
*/
public void kickPlayer(String playerName, String message) {
plugin.kickPlayer(playerName, message);
kickPlayer(getUuidFromName(playerName), message);
}
/**
@ -373,7 +363,7 @@ public abstract class AbstractRedisBungeeAPI {
* @since 0.8.0
*/
public void kickPlayer(UUID playerUUID, String message) {
plugin.kickPlayer(playerUUID, message);
this.plugin.playerDataManager().kickPlayer(playerUUID, message);
}
/**
@ -457,6 +447,7 @@ public abstract class AbstractRedisBungeeAPI {
/**
* 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

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

@ -0,0 +1,253 @@
/*
* 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.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.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public abstract class PlayerDataManager<P, LE, DE, PS extends IPubSubMessageEvent, SC extends IPlayerChangedServerNetworkEvent, NJE extends IPlayerLeftNetworkEvent, CE> {
protected final RedisBungeePlugin<P> plugin;
private final LoadingCache<UUID, String> serverCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis);
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 Object SERVERS_TO_PLAYERS_KEY = new Object();
private final LoadingCache<Object, Multimap<String, UUID>> serverToPlayersCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(this::serversToPlayersBuilder);
private final UnifiedJedis unifiedJedis;
public PlayerDataManager(RedisBungeePlugin<P> plugin) {
this.plugin = plugin;
this.unifiedJedis = plugin.proxyDataManager().unifiedJedis();
}
// handle network wide
// server change
public abstract void onPlayerChangedServerNetworkEvent(SC event);
public abstract void onNetworkPlayerQuit(NJE event);
// local events
public abstract void onPubSubMessageEvent(PS event);
public abstract void onServerConnectedEvent(CE event);
public abstract void onLoginEvent(LE event);
public abstract void onDisconnectEvent(DE event);
protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) {
this.serverCache.invalidate(event.getUuid());
}
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());
}
protected void handlePubSubMessageEvent(IPubSubMessageEvent event) {
// kick api
if (event.getChannel().equals("redisbungee-kick")) {
JSONObject data = new JSONObject(event.getMessage());
String proxy = data.getString("proxy");
if (proxy.equals(plugin.configuration().getProxyId())) {
return;
}
UUID uuid = UUID.fromString(data.getString("uuid"));
String message = data.getString("message");
plugin.handlePlatformKick(uuid, message);
return;
}
if (event.getChannel().equals("redisbungee-serverchange")) {
JSONObject data = new JSONObject(event.getMessage());
String proxy = data.getString("proxy");
if (proxy.equals(plugin.configuration().getProxyId())) {
return;
}
UUID uuid = UUID.fromString(data.getString("uuid"));
String from = data.getString("from");
String to = data.getString("to");
plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to));
return;
}
if (event.getChannel().equals("redisbungee-player-join")) {
JSONObject data = new JSONObject(event.getMessage());
String proxy = data.getString("proxy");
if (proxy.equals(plugin.configuration().getProxyId())) {
return;
}
UUID uuid = UUID.fromString(data.getString("uuid"));
plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid));
return;
}
if (event.getChannel().equals("redisbungee-player-leave")) {
JSONObject data = new JSONObject(event.getMessage());
String proxy = data.getString("proxy");
if (proxy.equals(plugin.configuration().getProxyId())) {
return;
}
UUID uuid = UUID.fromString(data.getString("uuid"));
plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid));
}
}
protected void playerChangedServer(UUID uuid, String from, String to) {
JSONObject data = new JSONObject();
data.put("proxy", plugin.configuration().getProxyId());
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);
}
public void kickPlayer(UUID uuid, String message) {
if (!plugin.handlePlatformKick(uuid, message)) { // handle locally before SENDING a message
JSONObject data = new JSONObject();
data.put("proxy", plugin.configuration().getProxyId());
data.put("uuid", uuid);
data.put("message", message);
plugin.proxyDataManager().sendChannelMessage("redisbungee-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("redis-bungee::player::" + uuid + "::data", data);
}
protected void addPlayer(final UUID uuid, final InetAddress inetAddress) {
Map<String, String> redisData = new HashMap<>();
redisData.put("last-online", String.valueOf(0));
redisData.put("proxy", plugin.configuration().getProxyId());
redisData.put("ip", inetAddress.toString());
unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", redisData);
JSONObject data = new JSONObject();
data.put("proxy", plugin.configuration().getProxyId());
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("redis-bungee::player::" + uuid + "::data", "last-online", String.valueOf(System.currentTimeMillis()));
unifiedJedis.hdel("redis-bungee::player::" + uuid + "::data", "server", "proxy", "ip");
JSONObject data = new JSONObject();
data.put("proxy", plugin.configuration().getProxyId());
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("redis-bungee::player::" + uuid + "::data", "proxy");
}
protected String getServerFromRedis(UUID uuid) {
return unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "server");
}
protected InetAddress getIpAddressFromRedis(UUID uuid) {
String ip = unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "ip");
if (ip == null) return null;
return InetAddresses.forString(ip);
}
protected long getLastOnlineFromRedis(UUID uuid) {
String unixString = unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "last-online");
if (unixString == null) return -1;
return Long.parseLong(unixString);
}
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) {
responses.put(uuid, pipeline.hget("redis-bungee::player::" + uuid + "::data", "server"));
}
pipeline.sync();
responses.forEach((uuid, response) -> builder.put(response.get(), uuid));
return builder.build();
}
@Override
public Multimap<String, UUID> clusterPipeline(ClusterPipeline pipeline) {
HashMap<UUID, Response<String>> responses = new HashMap<>();
for (UUID uuid : uuids) {
responses.put(uuid, pipeline.hget("redis-bungee::player::" + uuid + "::data", "server"));
}
pipeline.sync();
responses.forEach((uuid, response) -> builder.put(response.get(), uuid));
return builder.build();
}
}.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,383 @@
/*
* 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.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 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, AutoCloseable {
private static final String STREAM_ID = "redisbungee-stream";
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 proxyId;
// 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.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);
}
public List<String> proxiesIds() {
return Collections.list(this.heartbeats.keys());
}
public synchronized void sendCommandTo(String proxyToRun, String command) {
if (isClosed()) return;
publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command));
}
public synchronized void sendChannelMessage(String channel, String message) {
if (isClosed()) return;
this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message));
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::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::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;
}
// 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::proxies::" + proxyId() + "::online-players", removeString.toArray(new String[]{}));
pipeline.sadd("redisbungee::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::proxies::" + proxyId() + "::online-players", removeString.toArray(new String[]{}));
pipeline.sadd("redisbungee::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 ~10 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 > 10) {
deadProxies.add(id);
cleanProxy(id);
}
}
try {
new RedisPipelineTask<Void>(plugin) {
@Override
public Void doPooledPipeline(Pipeline pipeline) {
for (String deadProxy : deadProxies) {
pipeline.del("redisbungee::proxies::" + deadProxy + "::online-players");
}
pipeline.sync();
return null;
}
@Override
public Void clusterPipeline(ClusterPipeline pipeline) {
for (String deadProxy : deadProxies) {
pipeline.del("redisbungee::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));
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::proxies::" + this.proxyId + "::online-players", uuid.toString());
}
public void removePlayer(UUID uuid) {
this.unifiedJedis.srem("redisbungee::proxies::" + this.proxyId + "::online-players", uuid.toString());
}
private void destroyProxyMembers() {
unifiedJedis.del("redisbungee::proxies::" + this.proxyId + "::online-players");
}
private Set<UUID> getProxyMembers(String proxyId) {
Set<String> uuidsStrings = unifiedJedis.smembers("redisbungee::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);
}
break;
}
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) {
}
}
}
}
@Override
public void close() throws Exception {
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;
}
}

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

@ -10,28 +10,16 @@
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.UUID;
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
@ -51,225 +39,54 @@ public interface RedisBungeePlugin<P> extends EventsPlatform {
}
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();
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();
}
RedisBungeeMode getRedisBungeeMode();
AbstractRedisBungeeAPI getAbstractRedisBungeeApi();
ProxyDataManager proxyDataManager();
PlayerDataManager<P, ?, ?, ?, ?, ?, ?> playerDataManager();
UUIDTranslator getUuidTranslator();
Multimap<String, UUID> serverToPlayersCache();
boolean isOnlineMode();
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();
}
public P getPlayer(UUID uuid);
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();
}
public P getPlayer(String name);
default void sendProxyCommand(String proxyId, String command) {
checkArgument(getProxiesIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid");
sendChannelMessage("redisbungee-" + proxyId, command);
}
public UUID getPlayerUUID(String player);
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();
}
public String getPlayerName(UUID player);
PubSubListener getPubSubListener();
boolean handlePlatformKick(UUID uuid, String message);
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();
}
public String getPlayerServerName(P player);
public boolean isPlayerOnAServer(P player);
public InetAddress getPlayerIp(P player);
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

@ -12,11 +12,9 @@ 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 {
@ -47,6 +45,7 @@ public class RedisBungeeConfiguration {
this.overrideBungeeCommands = overrideBungeeCommands;
this.restoreOldKickBehavior = restoreOldKickBehavior;
}
public String getProxyId() {
return proxyId;
}

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

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

@ -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);
@ -53,22 +52,16 @@ public abstract class RedisTask<V> implements Runnable, Callable<V> {
this.execute();
}
public V execute(){
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

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

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

View File

@ -5,6 +5,7 @@ import com.google.common.annotations.VisibleForTesting;
@VisibleForTesting
public class RedisUtil {
public final static int PROXY_TIMEOUT = 30;
public static boolean isRedisVersionRight(String redisVersion) {
String[] args = redisVersion.split("\\.");
if (args.length < 2) {

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

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

@ -22,38 +22,38 @@ import java.util.List;
import java.util.UUID;
public class NameFetcher {
private static OkHttpClient httpClient;
private static final Gson gson = new Gson();
private static OkHttpClient httpClient;
private static final Gson gson = new Gson();
public static void setHttpClient(OkHttpClient httpClient) {
NameFetcher.httpClient = httpClient;
}
public static void setHttpClient(OkHttpClient httpClient) {
NameFetcher.httpClient = httpClient;
}
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 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();
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;
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();
}
return player.get("username").getAsString();
}
}

View File

@ -14,13 +14,15 @@ import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
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;

View File

@ -1,2 +1,3 @@
logged-in-other-location: "§cYou logged in from another location!"
already-logged-in: "§cYou are already logged in!"
already-logged-in: "§cYou are already logged in!"
error: "§cError has occurred"

View File

@ -40,11 +40,11 @@ tasks {
options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path)
}
runWaterfall {
waterfallVersion("1.19")
waterfallVersion("1.20")
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(8)
options.release.set(17)
}
javadoc {
options.encoding = Charsets.UTF_8.name()
@ -73,6 +73,7 @@ tasks {
relocate("com.google.gson", "com.imaginarycode.minecraft.redisbungee.internal.com.google.gson")
relocate("com.google.j2objc", "com.imaginarycode.minecraft.redisbungee.internal.com.google.j2objc")
relocate("com.google.thirdparty", "com.imaginarycode.minecraft.redisbungee.internal.com.google.thirdparty")
relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine")
}
}

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

@ -0,0 +1,98 @@
/*
* 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.PlayerDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class BungeePlayerDataManager extends PlayerDataManager<ProxiedPlayer, PostLoginEvent, PlayerDisconnectEvent, PubSubMessageEvent, PlayerChangedServerNetworkEvent, PlayerLeftNetworkEvent, ServerConnectedEvent> implements Listener {
public BungeePlayerDataManager(RedisBungeePlugin<ProxiedPlayer> plugin) {
super(plugin);
}
@Override
@EventHandler
public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) {
super.handleNetworkPlayerServerChange(event);
}
@Override
@EventHandler
public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) {
super.handleNetworkPlayerQuit(event);
}
@Override
@EventHandler
public void onPubSubMessageEvent(PubSubMessageEvent event) {
super.handlePubSubMessageEvent(event);
}
@Override
@EventHandler
public void onServerConnectedEvent(ServerConnectedEvent event) {
final String currentServer = event.getServer().getInfo().getName();
final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName();
super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer);
}
@EventHandler
public void onLoginEvent(LoginEvent event) {
event.registerIntent((Plugin) plugin);
// check if online
if (getLastOnline(event.getConnection().getUniqueId()) == 0) {
if (!plugin.configuration().restoreOldKickBehavior()) {
kickPlayer(event.getConnection().getUniqueId(), plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION));
// wait 3 seconds before releasing the event
plugin.executeAsyncAfter(() -> event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3);
} else {
event.setCancelled(true);
event.setCancelReason(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', Objects.requireNonNull(plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)))));
event.completeIntent((Plugin) plugin);
}
} else {
event.completeIntent((Plugin) plugin);
}
}
@Override
@EventHandler
public void onLoginEvent(PostLoginEvent event) {
super.addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getAddress().getAddress());
}
@Override
@EventHandler
public void onDisconnectEvent(PlayerDisconnectEvent event) {
super.removePlayer(event.getPlayer().getUniqueId());
}
}

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

@ -10,131 +10,91 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager;
import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
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.*;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands;
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.*;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import com.squareup.okhttp.Dispatcher;
import com.squareup.okhttp.OkHttpClient;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
import net.md_5.bungee.api.plugin.Plugin;
import redis.clients.jedis.*;
import net.md_5.bungee.api.scheduler.ScheduledTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool;
import java.io.*;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.util.*;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlayer>, ConfigLoader {
private static RedisBungeeAPI apiStatic;
private AbstractRedisBungeeAPI api;
private RedisBungeeMode redisBungeeMode;
private PubSubListener psl = null;
private ProxyDataManager proxyDataManager;
private BungeePlayerDataManager playerDataManager;
private ScheduledTask heartbeatTask;
private ScheduledTask cleanupTask;
private Summoner<?> summoner;
private UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private BungeeDataManager dataManager;
private OkHttpClient httpClient;
private volatile List<String> proxiesIds;
private final AtomicInteger globalPlayerCount = new AtomicInteger();
private Future<?> integrityCheck;
private Future<?> heartbeatTask;
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
private final Logger logger = LoggerFactory.getLogger("RedisBungee");
@Override
public RedisBungeeConfiguration getConfiguration() {
public RedisBungeeConfiguration configuration() {
return this.configuration;
}
@Override
public int getCount() {
return this.globalPlayerCount.get();
}
@Override
public Set<String> getLocalPlayersAsUuidStrings() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (ProxiedPlayer player : getProxy().getPlayers()) {
builder.add(player.getUniqueId().toString());
}
return builder.build();
}
@Override
public AbstractDataManager<ProxiedPlayer, ?, ?, ?> getDataManager() {
return this.dataManager;
}
@Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() {
return this.api;
}
@Override
public ProxyDataManager proxyDataManager() {
return this.proxyDataManager;
}
@Override
public PlayerDataManager<ProxiedPlayer, ?, ?, ?, ?, ?, ?> playerDataManager() {
return this.playerDataManager;
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@Override
public Multimap<String, UUID> serverToPlayersCache() {
try {
return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> getProxiesIds() {
return proxiesIds;
}
@Override
public PubSubListener getPubSubListener() {
return this.psl;
}
@Override
public void executeAsync(Runnable runnable) {
this.getProxy().getScheduler().runAsync(this, runnable);
}
@Override
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit);
}
@Override
public void fireEvent(Object event) {
this.getProxy().getPluginManager().callEvent((Event) event);
@ -147,17 +107,32 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlay
@Override
public void logInfo(String msg) {
this.getLogger().info(msg);
this.logger.info(msg);
}
@Override
public void logInfo(String format, Object... object) {
this.logger.info(format, object);
}
@Override
public void logWarn(String msg) {
this.getLogger().warning(msg);
this.logger.warn(msg);
}
@Override
public void logWarn(String format, Object... object) {
this.logger.warn(format, object);
}
@Override
public void logFatal(String msg) {
this.getLogger().severe(msg);
this.logger.error(msg);
}
@Override
public void logFatal(String format, Throwable throwable) {
this.logger.error(format, throwable);
}
@Override
@ -180,6 +155,15 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlay
return this.getProxy().getPlayer(player).getName();
}
@Override
public boolean handlePlatformKick(UUID uuid, String message) {
ProxiedPlayer player = getPlayer(uuid);
if (player == null) return false;
if (!player.isConnected()) return false;
player.disconnect(TextComponent.fromLegacyText(message));
return true;
}
@Override
public String getPlayerServerName(ProxiedPlayer player) {
return player.getServer().getInfo().getName();
@ -209,16 +193,38 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlay
builtinService.shutdownNow();
} catch (IllegalAccessException | NoSuchFieldException e) {
getLogger().log(Level.WARNING, "Can't replace BungeeCord thread pool with our own");
getLogger().log(Level.INFO, "skipping replacement.....");
getLogger().log(Level.WARNING, "skipping replacement.....");
}
try {
loadConfig(this, getDataFolder());
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
}
// init the api class
this.api = new RedisBungeeAPI(this);
apiStatic = (RedisBungeeAPI) this.api;
// init the proxy data manager
this.proxyDataManager = new ProxyDataManager(this) {
@Override
public Set<UUID> getLocalOnlineUUIDs() {
HashSet<UUID> uuids = new HashSet<>();
ProxyServer.getInstance().getPlayers().forEach((proxiedPlayer) -> uuids.add(proxiedPlayer.getUniqueId()));
return uuids;
}
@Override
protected void handlePlatformCommandExecution(String command) {
logInfo("Dispatching {}", command);
ProxyServer.getInstance().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), command);
}
};
this.playerDataManager = new BungeePlayerDataManager(this);
getProxy().getPluginManager().registerListener(this, this.playerDataManager);
getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this));
// start listening
getProxy().getScheduler().runAsync(this, proxyDataManager);
// heartbeat
this.heartbeatTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.publishHeartbeat(), 0, 1, TimeUnit.SECONDS);
// cleanup
this.cleanupTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.correctionTask(), 0, 60, TimeUnit.SECONDS);
// init the http lib
httpClient = new OkHttpClient();
Dispatcher dispatcher = new Dispatcher(getExecutorService());
@ -226,29 +232,7 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlay
NameFetcher.setHttpClient(httpClient);
UUIDFetcher.setHttpClient(httpClient);
InitialUtils.checkRedisVersion(this);
// check if this proxy is recovering from a crash and start heart the beat.
InitialUtils.checkIfRecovering(this, getDataFolder().toPath());
updateProxiesIds();
uuidTranslator = new UUIDTranslator(this);
heartbeatTask = service.scheduleAtFixedRate(new HeartbeatTask(this, this.globalPlayerCount), 0, HeartbeatTask.INTERVAL, HeartbeatTask.REPEAT_INTERVAL_TIME_UNIT);
dataManager = new BungeeDataManager(this);
getProxy().getPluginManager().registerListener(this, new RedisBungeeBungeeListener(this, configuration.getExemptAddresses()));
getProxy().getPluginManager().registerListener(this, dataManager);
psl = new PubSubListener(this);
getProxy().getScheduler().runAsync(this, psl);
IntegrityCheckTask integrityCheckTask = new IntegrityCheckTask(this) {
@Override
public void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis) {
ProxiedPlayer proxiedPlayer = ProxyServer.getInstance().getPlayer(UUID.fromString(player));
if (proxiedPlayer == null)
return; // We'll deal with it later.
BungeePlayerUtils.createBungeePlayer(proxiedPlayer, unifiedJedis, false);
}
};
integrityCheck = service.scheduleAtFixedRate(integrityCheckTask::execute, 0, IntegrityCheckTask.INTERVAL, IntegrityCheckTask.TIMEUNIT);
// register plugin messages channel.
getProxy().registerChannel("legacy:redisbungee");
@ -267,30 +251,35 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlay
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlayerProxyCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlistCommand(this));
}
// init the api
this.api = new RedisBungeeAPI(this);
apiStatic = (RedisBungeeAPI) this.api;
logInfo("RedisBungee initialized successfully ");
}
@Override
public void stop() {
logInfo("Turning off redis connections.....");
// Poison the PubSub listener
if (psl != null) {
psl.poison();
}
if (integrityCheck != null) {
integrityCheck.cancel(true);
getProxy().getPluginManager().unregisterListeners(this);
if (this.cleanupTask != null) {
this.cleanupTask.cancel();
}
if (heartbeatTask != null) {
heartbeatTask.cancel(true);
heartbeatTask.cancel();
}
try {
this.proxyDataManager.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
getProxy().getPluginManager().unregisterListeners(this);
ShutdownUtils.shutdownCleanup(this);
try {
this.summoner.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
logInfo("RedisBungee shutdown");
logInfo("RedisBungee shutdown successfully");
}
@Override
@ -303,10 +292,14 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlay
return this.redisBungeeMode;
}
@Override
public void executeAsync(Runnable runnable) {
this.getProxy().getScheduler().runAsync(this, runnable);
}
@Override
public void updateProxiesIds() {
proxiesIds = getCurrentProxiesIds(false);
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit);
}
@Override
@ -349,9 +342,8 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlay
/**
* This returns an instance of {@link RedisBungeeAPI}
*
* @deprecated Please use {@link RedisBungeeAPI#getRedisBungeeApi()} this class intended to for old plugins that no longer updated.
*
* @return the {@link AbstractRedisBungeeAPI} object instance.
* @deprecated Please use {@link RedisBungeeAPI#getRedisBungeeApi()} this class intended to for old plugins that no longer updated.
*/
@Deprecated
public static RedisBungeeAPI getApi() {
@ -362,4 +354,6 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlay
public JedisPool getPool() {
return api.getJedisPool();
}
}

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

@ -0,0 +1,154 @@
/*
* 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.RedisBungeePlugin;
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.PluginMessageEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import java.util.*;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset;
public class RedisBungeeListener implements Listener {
private final RedisBungeePlugin<ProxiedPlayer> plugin;
public RedisBungeeListener(RedisBungeePlugin<ProxiedPlayer> plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPing(ProxyPingEvent event) {
if (plugin.configuration().getExemptAddresses().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.proxyDataManager().totalNetworkPlayers());
}
@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.proxyDataManager().networkPlayers();
} 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));
}
case "PlayerCount" -> {
out.writeUTF("PlayerCount");
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
out.writeInt(plugin.proxyDataManager().totalNetworkPlayers());
} else {
out.writeUTF(type);
try {
out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size());
} catch (IllegalArgumentException e) {
out.writeInt(0);
}
}
}
case "LastOnline" -> {
String user = in.readUTF();
out.writeUTF("LastOnline");
out.writeUTF(user);
out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true))));
}
case "ServerPlayers" -> {
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
boolean includesUsers;
switch (type1) {
case "COUNT" -> includesUsers = false;
case "PLAYERS" -> includesUsers = true;
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);
}
}
case "Proxy" -> {
out.writeUTF("Proxy");
out.writeUTF(plugin.configuration().getProxyId());
}
case "PlayerProxy" -> {
String username = in.readUTF();
out.writeUTF("PlayerProxy");
out.writeUTF(username);
out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true))));
}
default -> {
return;
}
}
((Server) event.getSender()).sendData(currentChannel, out.toByteArray());
});
}
}
}

View File

@ -13,8 +13,8 @@ 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 com.imaginarycode.minecraft.redisbungee.RedisBungee;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
@ -286,9 +286,10 @@ public class RedisBungeeCommands {
public static class ServerIds extends Command {
private final RedisBungee plugin;
public ServerIds(RedisBungee plugin) {
super("serverids", "redisbungee.command.serverids");
this.plugin =plugin;
this.plugin = plugin;
}
@Override
@ -313,8 +314,8 @@ public class RedisBungeeCommands {
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)) {
String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId();
if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) {
sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create());
return;
}

View File

@ -39,7 +39,7 @@ tasks {
"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)
options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path)
}
runVelocity {
velocityVersion("3.3.0-SNAPSHOT")
@ -61,6 +61,7 @@ tasks {
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")
relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine")
}
}

View File

@ -0,0 +1,156 @@
/*
* 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.RedisBungeePlugin;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
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 java.util.*;
import java.util.stream.Collectors;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset;
public class RedisBungeeListener {
private final RedisBungeePlugin<Player> plugin;
public RedisBungeeListener(RedisBungeePlugin<Player> plugin) {
this.plugin = plugin;
}
@Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last
public void onPing(ProxyPingEvent event) {
if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getRemoteAddress().getAddress())) {
return;
}
ServerPing.Builder ping = event.getPing().asBuilder();
ping.onlinePlayers(plugin.proxyDataManager().totalNetworkPlayers());
event.setPing(ping.build());
}
@Subscribe
public void onPluginMessage(PluginMessageEvent event) {
if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) {
return;
}
event.setResult(PluginMessageEvent.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.proxyDataManager().networkPlayers();
} 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.proxyDataManager().totalNetworkPlayers());
} 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;
case "PLAYERS" -> includesUsers = true;
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.configuration().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());
});
}
}

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

@ -10,12 +10,11 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import com.imaginarycode.minecraft.redisbungee.api.*;
import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager;
import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
@ -23,7 +22,7 @@ import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEv
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.tasks.*;
import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
@ -46,17 +45,21 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.scheduler.ScheduledTask;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.slf4j.Logger;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Plugin(id = "redisbungee", name = "RedisBungee", version = Constants.VERSION, url = "https://github.com/ProxioDev/RedisBungee", authors = {"astei", "ProxioDev"})
public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, ConfigLoader {
@ -64,29 +67,25 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
private final Logger logger;
private final Path dataFolder;
private final AbstractRedisBungeeAPI api;
private final PubSubListener psl;
private Summoner<?> jedisSummoner;
private RedisBungeeMode redisBungeeMode;
private final UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private final VelocityDataManager dataManager;
private final OkHttpClient httpClient;
private volatile List<String> proxiesIds;
private final AtomicInteger globalPlayerCount = new AtomicInteger();
private ScheduledTask integrityCheck;
private final ProxyDataManager proxyDataManager;
private final VelocityPlayerDataManager playerDataManager;
private ScheduledTask cleanUpTask;
private ScheduledTask heartbeatTask;
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
public static final List<ChannelIdentifier> IDENTIFIERS = List.of(
MinecraftChannelIdentifier.create("legacy", "redisbungee"),
new LegacyChannelIdentifier("RedisBungee"),
// This is needed for clients before 1.13
new LegacyChannelIdentifier("legacy:redisbungee")
);
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
@Inject
public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
@ -102,11 +101,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
}
this.api = new RedisBungeeAPI(this);
InitialUtils.checkRedisVersion(this);
// check if this proxy is recovering from a crash and start heart the beat.
InitialUtils.checkIfRecovering(this, getDataFolder());
this.proxyDataManager = new ProxyDataManager(this) {
@Override
public Set<UUID> getLocalOnlineUUIDs() {
HashSet<UUID> players = new HashSet<>();
server.getAllPlayers().forEach(player -> players.add(player.getUniqueId()));
return players;
}
@Override
protected void handlePlatformCommandExecution(String command) {
server.getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), command);
}
};
this.playerDataManager = new VelocityPlayerDataManager(this);
uuidTranslator = new UUIDTranslator(this);
dataManager = new VelocityDataManager(this);
psl = new PubSubListener(this);
this.httpClient = new OkHttpClient();
Dispatcher dispatcher = new Dispatcher(Executors.newFixedThreadPool(6));
this.httpClient.setDispatcher(dispatcher);
@ -115,31 +124,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
}
@Override
public RedisBungeeConfiguration getConfiguration() {
return this.configuration;
}
@Override
public int getCount() {
return this.globalPlayerCount.get();
}
@Override
public Set<String> getLocalPlayersAsUuidStrings() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (Player player : getProxy().getAllPlayers()) {
builder.add(player.getUniqueId().toString());
}
return builder.build();
}
@Override
public AbstractDataManager<Player, ?, ?, ?> getDataManager() {
return this.dataManager;
}
@Override
public Summoner<?> getSummoner() {
return this.jedisSummoner;
@ -150,29 +134,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
return this.api;
}
@Override
public ProxyDataManager proxyDataManager() {
return this.proxyDataManager;
}
@Override
public PlayerDataManager<Player, ?, ?, ?, ?, ?, ?> playerDataManager() {
return this.playerDataManager;
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@Override
public Multimap<String, UUID> serverToPlayersCache() {
try {
return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> getProxiesIds() {
return proxiesIds;
}
@Override
public PubSubListener getPubSubListener() {
return this.psl;
}
@Override
public void executeAsync(Runnable runnable) {
@ -199,16 +175,36 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
this.getLogger().info(msg);
}
@Override
public void logInfo(String format, Object... object) {
logger.info(format, object);
}
@Override
public void logWarn(String msg) {
this.getLogger().warn(msg);
}
@Override
public void logWarn(String format, Object... object) {
logger.warn(format, object);
}
@Override
public void logFatal(String msg) {
this.getLogger().error(msg);
}
@Override
public void logFatal(String format, Throwable throwable) {
logger.error(format, throwable);
}
@Override
public RedisBungeeConfiguration configuration() {
return this.configuration;
}
@Override
public Player getPlayer(UUID uuid) {
return this.getProxy().getPlayer(uuid).orElse(null);
@ -229,6 +225,16 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null);
}
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection();
@Override
public boolean handlePlatformKick(UUID uuid, String message) {
Player player = getPlayer(uuid);
if (player == null) return false;
player.disconnect(serializer.deserialize(message));
return true;
}
@Override
public String getPlayerServerName(Player player) {
return player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
@ -247,25 +253,16 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
@Override
public void initialize() {
logInfo("Initializing RedisBungee.....");
updateProxiesIds();
// start heartbeat task
heartbeatTask = getProxy().getScheduler().buildTask(this, new HeartbeatTask(this, this.globalPlayerCount)).repeat(HeartbeatTask.INTERVAL, HeartbeatTask.REPEAT_INTERVAL_TIME_UNIT).schedule();
// heartbeat and clean up
this.heartbeatTask = server.getScheduler().buildTask(this, this.proxyDataManager::publishHeartbeat).repeat(Duration.ofSeconds(1)).schedule();
this.cleanUpTask = server.getScheduler().buildTask(this, this.proxyDataManager::correctionTask).repeat(Duration.ofSeconds(60)).schedule();
getProxy().getEventManager().register(this, new RedisBungeeVelocityListener(this, configuration.getExemptAddresses()));
getProxy().getEventManager().register(this, dataManager);
getProxy().getScheduler().buildTask(this, psl).schedule();
IntegrityCheckTask integrityCheckTask = new IntegrityCheckTask(this) {
@Override
public void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis) {
Player playerProxied = getProxy().getPlayer(UUID.fromString(player)).orElse(null);
if (playerProxied == null)
return; // We'll deal with it later.
VelocityPlayerUtils.createVelocityPlayer(playerProxied, unifiedJedis, false);
}
};
integrityCheck = getProxy().getScheduler().buildTask(this, integrityCheckTask::execute).repeat(30, TimeUnit.SECONDS).schedule();
server.getEventManager().register(this, this.playerDataManager);
server.getEventManager().register(this, new RedisBungeeListener(this));
// subscribe
server.getScheduler().buildTask(this, this.proxyDataManager).schedule();
// register plugin messages
IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register);
@ -292,19 +289,18 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
public void stop() {
logInfo("Turning off redis connections.....");
// Poison the PubSub listener
if (psl != null) {
psl.poison();
}
if (integrityCheck != null) {
integrityCheck.cancel();
if (cleanUpTask != null) {
cleanUpTask.cancel();
}
if (heartbeatTask != null) {
heartbeatTask.cancel();
}
ShutdownUtils.shutdownCleanup(this);
try {
this.proxyDataManager.close();
this.jedisSummoner.close();
} catch (IOException e) {
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -331,10 +327,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
return this.redisBungeeMode;
}
@Override
public void updateProxiesIds() {
this.proxiesIds = this.getCurrentProxiesIds(false);
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyInitializeEvent(ProxyInitializeEvent event) {

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

@ -0,0 +1,100 @@
/*
* 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.PlayerDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.velocitypowered.api.event.Continuation;
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.PostLoginEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class VelocityPlayerDataManager extends PlayerDataManager<Player, PostLoginEvent, DisconnectEvent, PubSubMessageEvent, PlayerChangedServerNetworkEvent, PlayerLeftNetworkEvent, ServerConnectedEvent> {
public VelocityPlayerDataManager(RedisBungeePlugin<Player> plugin) {
super(plugin);
}
@Override
@Subscribe
public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) {
handleNetworkPlayerServerChange(event);
}
@Override
@Subscribe
public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) {
handleNetworkPlayerQuit(event);
}
@Override
@Subscribe
public void onPubSubMessageEvent(PubSubMessageEvent event) {
handlePubSubMessageEvent(event);
}
@Override
@Subscribe
public void onServerConnectedEvent(ServerConnectedEvent event) {
final String currentServer = event.getServer().getServerInfo().getName();
final String oldServer;
if (event.getPreviousServer().isPresent()) {
oldServer = event.getPreviousServer().get().getServerInfo().getName();
} else {
oldServer = null;
}
super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer);
}
private static final LegacyComponentSerializer LEGACY_COMPONENT_SERIALIZER = LegacyComponentSerializer.builder().build();
@Subscribe
public void onLoginEvent(LoginEvent event, Continuation continuation) {
// check if online
if (getLastOnline(event.getPlayer().getUniqueId()) == 0) {
if (!plugin.configuration().restoreOldKickBehavior()) {
kickPlayer(event.getPlayer().getUniqueId(), plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION));
// wait 3 seconds before releasing the event
plugin.executeAsyncAfter(continuation::resume, TimeUnit.SECONDS, 3);
} else {
event.setResult(ResultedEvent.ComponentResult.denied(LEGACY_COMPONENT_SERIALIZER.deserialize(Objects.requireNonNull(plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)))));
continuation.resume();
}
} else {
continuation.resume();
}
}
@Override
@Subscribe
public void onLoginEvent(PostLoginEvent event) {
addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getRemoteAddress().getAddress());
}
@Override
@Subscribe
public void onDisconnectEvent(DisconnectEvent event) {
if (event.getLoginStatus() == DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN || event.getLoginStatus() == DisconnectEvent.LoginStatus.PRE_SERVER_JOIN) {
removePlayer(event.getPlayer().getUniqueId());
}
}
}

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

@ -10,12 +10,6 @@
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;
@ -25,11 +19,16 @@ 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;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
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.
@ -324,8 +323,8 @@ public class RedisBungeeCommands {
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)) {
String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId();
if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) {
sender.sendMessage(Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED));
return;
}

View File

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