package tc.oc.pgm.listing; import java.security.SecureRandom; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import com.google.common.util.concurrent.ListenableFuture; import net.md_5.bungee.api.chat.TranslatableComponent; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import tc.oc.api.http.HttpClient; import tc.oc.api.http.HttpOption; import tc.oc.api.message.types.Reply; import tc.oc.commons.bukkit.chat.Audiences; import tc.oc.commons.core.commands.CommandFutureCallback; import tc.oc.commons.core.concurrent.Flexecutor; import tc.oc.minecraft.api.event.Enableable; import tc.oc.minecraft.api.server.LocalServer; import tc.oc.minecraft.scheduler.Sync; @Singleton class ListingServiceImpl implements ListingService, Enableable { private final HttpClient http; private final ListingConfiguration config; private final LocalServer localServer; private final SecureRandom random = new SecureRandom(); private final Flexecutor executor; private final Audiences audiences; private final ConsoleCommandSender console; private boolean online; private @Nullable String sessionId; private @Nullable String sessionDigest; @Inject ListingServiceImpl(HttpClient http, ListingConfiguration config, LocalServer localServer, @Sync(defer = true) Flexecutor executor, Audiences audiences, ConsoleCommandSender console) { this.http = http; this.config = config; this.localServer = localServer; this.executor = executor; this.audiences = audiences; this.console = console; } @Override public @Nullable String sessionDigest() { return sessionDigest; } @Override public void enable() { if(config.enabled()) { // Don't announce until we are ready to receive the ping executor.execute(() -> update(true)); } } @Override public void disable() { if(online) { update(false); } } @Override public ListenableFuture<Reply> update(boolean online) { return update(online, console); } @Override public ListenableFuture<Reply> update(boolean online, CommandSender sender) { this.online = online; if(sessionId == null) { final byte[] bytes = new byte[20]; random.nextBytes(bytes); sessionId = Hex.encodeHexString(bytes); sessionDigest = DigestUtils.sha1Hex(sessionId); } final ListenableFuture<Reply> future = http.post(config.announceUrl().toString(), new ListingUpdate() { @Override public @Nullable String host() { return config.serverHost(); } @Override public int port() { return config.serverPort().orElseGet(localServer::getPort); } @Override public boolean online() { return online; } @Override public String session() { return sessionId; } }, Reply.class, HttpOption.INFINITE_RETRY); executor.callback( future, CommandFutureCallback.onSuccess(sender, reply -> { if(!online) { sessionId = sessionDigest = null; } audiences.get(sender).sendMessage(new TranslatableComponent(online ? "announce.online" : "announce.offline")); }) ); return future; } }