mirror of
https://github.com/OvercastNetwork/ProjectAres.git
synced 2025-04-11 22:56:08 +02:00
Announce support
This commit is contained in:
parent
0562d80699
commit
b883ef5799
@ -3,6 +3,7 @@ package tc.oc.api;
|
||||
import tc.oc.api.document.DocumentsManifest;
|
||||
import tc.oc.api.engagement.EngagementModelManifest;
|
||||
import tc.oc.api.games.GameModelManifest;
|
||||
import tc.oc.api.http.HttpManifest;
|
||||
import tc.oc.api.maps.MapModelManifest;
|
||||
import tc.oc.api.match.MatchModelManifest;
|
||||
import tc.oc.api.message.MessagesManifest;
|
||||
@ -29,6 +30,7 @@ public final class ApiManifest extends HybridManifest {
|
||||
install(new DocumentsManifest());
|
||||
install(new MessagesManifest());
|
||||
install(new ModelsManifest());
|
||||
install(new HttpManifest());
|
||||
|
||||
install(new ServerModelManifest());
|
||||
install(new UserModelManifest());
|
||||
|
@ -3,6 +3,8 @@ package tc.oc.api.http;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executors;
|
||||
@ -105,10 +107,6 @@ public class HttpClient implements Connectable {
|
||||
});
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return this.config.getBaseUrl();
|
||||
}
|
||||
|
||||
public ListenableFuture<?> get(String path, HttpOption... options) {
|
||||
return get(path, (TypeToken) null, options);
|
||||
}
|
||||
@ -166,11 +164,17 @@ public class HttpClient implements Connectable {
|
||||
}
|
||||
|
||||
protected <T> ListenableFuture<T> request(String method, String path, @Nullable Object content, @Nullable TypeToken<T> returnType, HttpOption...options) {
|
||||
final GenericUrl url;
|
||||
try {
|
||||
url = new GenericUrl(new URL(config.getBaseUrl(), path));
|
||||
} catch(MalformedURLException e) {
|
||||
throw new IllegalArgumentException(e.getMessage());
|
||||
}
|
||||
|
||||
// NOTE: Serialization must happen synchronously, because getter methods may not be thread-safe
|
||||
final HttpContent httpContent = content == null ? null : new Content(gson.toJson(content));
|
||||
|
||||
GenericUrl url = new GenericUrl(this.getBaseUrl() + path);
|
||||
HttpRequest request;
|
||||
final HttpRequest request;
|
||||
try {
|
||||
request = requestFactory.buildRequest(method, url, httpContent).setThrowExceptionOnExecuteError(false);
|
||||
} catch (IOException e) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package tc.oc.api.http;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public interface HttpClientConfiguration {
|
||||
|
||||
int DEFAULT_THREADS = 0;
|
||||
@ -10,7 +12,7 @@ public interface HttpClientConfiguration {
|
||||
/**
|
||||
* Base URL of the API. End points will be appended to this address.
|
||||
*/
|
||||
String getBaseUrl();
|
||||
URL getBaseUrl();
|
||||
|
||||
/**
|
||||
* Number of threads to execute requests. 0 indicates an unbounded number
|
||||
|
@ -1,7 +1,9 @@
|
||||
package tc.oc.api.http;
|
||||
|
||||
import java.net.URL;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import tc.oc.commons.core.configuration.ConfigUtils;
|
||||
import tc.oc.minecraft.api.configuration.Configuration;
|
||||
import tc.oc.minecraft.api.configuration.ConfigurationSection;
|
||||
|
||||
@ -24,8 +26,8 @@ public class HttpClientConfigurationImpl implements HttpClientConfiguration {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return config.getString(BASE_URL_PATH);
|
||||
public URL getBaseUrl() {
|
||||
return ConfigUtils.needUrl(config, BASE_URL_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,7 +7,7 @@ public class HttpManifest extends HybridManifest {
|
||||
@Override
|
||||
protected void configure() {
|
||||
expose(HttpClient.class);
|
||||
bind(HttpClient.class);
|
||||
bind(HttpClient.class).asEagerSingleton();
|
||||
bind(HttpClientConfiguration.class)
|
||||
.to(HttpClientConfigurationImpl.class);
|
||||
}
|
||||
|
@ -33,5 +33,3 @@ queue:
|
||||
logging:
|
||||
root:
|
||||
level: INFO
|
||||
tc-oc-api-bukkit-BukkitApi:
|
||||
level: INFO
|
||||
|
@ -1,6 +1,5 @@
|
||||
package tc.oc.api.ocn;
|
||||
|
||||
import tc.oc.api.http.HttpManifest;
|
||||
import tc.oc.api.minecraft.queue.MinecraftQueueManifest;
|
||||
import tc.oc.api.model.ModelBinders;
|
||||
import tc.oc.commons.core.inject.HybridManifest;
|
||||
@ -11,6 +10,5 @@ public class OCNApiManifest extends HybridManifest implements ModelBinders {
|
||||
protected void configure() {
|
||||
install(new OCNModelsManifest());
|
||||
install(new MinecraftQueueManifest());
|
||||
install(new HttpManifest());
|
||||
}
|
||||
}
|
||||
|
@ -223,4 +223,7 @@ tnt.license.use.restricted = You need a TNT license to use TNT or Redstone on th
|
||||
|
||||
item.locked = This item cannot be removed from its slot
|
||||
|
||||
stats.hotbar = {0} kills ({1} streak) {2} deaths {3} K/D
|
||||
stats.hotbar = {0} kills ({1} streak) {2} deaths {3} K/D
|
||||
|
||||
announce.online = Announced server as online
|
||||
announce.offline = Announced server as offline
|
||||
|
@ -19,6 +19,7 @@ import tc.oc.pgm.freeze.FreezeListener;
|
||||
import tc.oc.pgm.listeners.BlockTransformListener;
|
||||
import tc.oc.pgm.listeners.MatchAnnouncer;
|
||||
import tc.oc.pgm.listeners.PGMListener;
|
||||
import tc.oc.pgm.listing.ListingManifest;
|
||||
import tc.oc.pgm.map.MapLibrary;
|
||||
import tc.oc.pgm.map.MapLibraryImpl;
|
||||
import tc.oc.pgm.map.MapLoader;
|
||||
@ -56,6 +57,8 @@ public final class PGMManifest extends HybridManifest {
|
||||
install(new MatchPlayerEventRouter.Manifest());
|
||||
install(new MatchAnalyticsManifest());
|
||||
|
||||
install(new ListingManifest());
|
||||
|
||||
bind(MatchManager.class);
|
||||
bind(MatchLoader.class);
|
||||
bind(MatchFinder.class).to(MatchLoader.class);
|
||||
|
34
PGM/src/main/java/tc/oc/pgm/listing/ListingCommands.java
Normal file
34
PGM/src/main/java/tc/oc/pgm/listing/ListingCommands.java
Normal file
@ -0,0 +1,34 @@
|
||||
package tc.oc.pgm.listing;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.sk89q.minecraft.util.commands.Command;
|
||||
import com.sk89q.minecraft.util.commands.CommandContext;
|
||||
import com.sk89q.minecraft.util.commands.CommandException;
|
||||
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
||||
import com.sk89q.minecraft.util.commands.SuggestException;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
@Singleton
|
||||
public class ListingCommands {
|
||||
|
||||
private final ListingService listingService;
|
||||
|
||||
@Inject ListingCommands(ListingService listingService) {
|
||||
this.listingService = listingService;
|
||||
}
|
||||
|
||||
@Command(
|
||||
aliases = "announce",
|
||||
usage = "[on|off]",
|
||||
desc = "Announce the server to the public listing service",
|
||||
min = 0,
|
||||
max = 1
|
||||
)
|
||||
@CommandPermissions("pgm.listing.announce")
|
||||
public void announce(CommandContext args, CommandSender sender) throws CommandException, SuggestException {
|
||||
listingService.update("on".equals(args.tryString(0, ImmutableList.of("on", "off")).orElse("on")), sender);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package tc.oc.pgm.listing;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.OptionalInt;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import tc.oc.commons.core.configuration.ConfigUtils;
|
||||
import tc.oc.minecraft.api.configuration.Configuration;
|
||||
import tc.oc.minecraft.api.configuration.ConfigurationSection;
|
||||
import tc.oc.net.UriUtils;
|
||||
|
||||
class ListingConfiguration {
|
||||
|
||||
private final ConfigurationSection config;
|
||||
|
||||
@Inject ListingConfiguration(Configuration root) {
|
||||
this.config = root.needSection("announce");
|
||||
}
|
||||
|
||||
public boolean enabled() {
|
||||
return config.getBoolean("enabled", false);
|
||||
}
|
||||
|
||||
public URL announceUrl() {
|
||||
return ConfigUtils.getUrl(config, "url", UriUtils.url("https://oc.tc/announce"));
|
||||
}
|
||||
|
||||
public @Nullable String serverHost() {
|
||||
return config.getString("server-host");
|
||||
}
|
||||
|
||||
public OptionalInt serverPort() {
|
||||
final int port = config.getInt("server-port", 0);
|
||||
return port != 0 ? OptionalInt.of(port) : OptionalInt.empty();
|
||||
}
|
||||
}
|
21
PGM/src/main/java/tc/oc/pgm/listing/ListingManifest.java
Normal file
21
PGM/src/main/java/tc/oc/pgm/listing/ListingManifest.java
Normal file
@ -0,0 +1,21 @@
|
||||
package tc.oc.pgm.listing;
|
||||
|
||||
import tc.oc.commons.core.commands.CommandBinder;
|
||||
import tc.oc.commons.core.inject.HybridManifest;
|
||||
import tc.oc.minecraft.api.event.ListenerBinder;
|
||||
|
||||
public class ListingManifest extends HybridManifest {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ListingConfiguration.class);
|
||||
bind(ListingService.class).to(ListingServiceImpl.class);
|
||||
|
||||
final ListenerBinder listeners = new ListenerBinder(binder());
|
||||
listeners.bindListener().to(PingListener.class);
|
||||
listeners.bindListener().to(ListingServiceImpl.class);
|
||||
|
||||
new CommandBinder(binder())
|
||||
.register(ListingCommands.class);
|
||||
}
|
||||
}
|
16
PGM/src/main/java/tc/oc/pgm/listing/ListingService.java
Normal file
16
PGM/src/main/java/tc/oc/pgm/listing/ListingService.java
Normal file
@ -0,0 +1,16 @@
|
||||
package tc.oc.pgm.listing;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import tc.oc.api.message.types.Reply;
|
||||
|
||||
public interface ListingService {
|
||||
|
||||
ListenableFuture<Reply> update(boolean online);
|
||||
|
||||
ListenableFuture<Reply> update(boolean online, CommandSender sender);
|
||||
|
||||
@Nullable String sessionDigest();
|
||||
}
|
118
PGM/src/main/java/tc/oc/pgm/listing/ListingServiceImpl.java
Normal file
118
PGM/src/main/java/tc/oc/pgm/listing/ListingServiceImpl.java
Normal file
@ -0,0 +1,118 @@
|
||||
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;
|
||||
}
|
||||
}
|
18
PGM/src/main/java/tc/oc/pgm/listing/ListingUpdate.java
Normal file
18
PGM/src/main/java/tc/oc/pgm/listing/ListingUpdate.java
Normal file
@ -0,0 +1,18 @@
|
||||
package tc.oc.pgm.listing;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import tc.oc.api.annotations.Serialize;
|
||||
import tc.oc.api.docs.virtual.Document;
|
||||
|
||||
@Serialize
|
||||
public interface ListingUpdate extends Document {
|
||||
|
||||
@Nullable String host();
|
||||
|
||||
int port();
|
||||
|
||||
boolean online();
|
||||
|
||||
String session();
|
||||
}
|
54
PGM/src/main/java/tc/oc/pgm/listing/PingListener.java
Normal file
54
PGM/src/main/java/tc/oc/pgm/listing/PingListener.java
Normal file
@ -0,0 +1,54 @@
|
||||
package tc.oc.pgm.listing;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.server.ServerListPingEvent;
|
||||
import tc.oc.minecraft.api.event.Enableable;
|
||||
import tc.oc.pgm.match.Match;
|
||||
|
||||
@Singleton
|
||||
public class PingListener implements Listener, Enableable {
|
||||
|
||||
private final Provider<Match> matchProvider;
|
||||
private final ListingService listingService;
|
||||
|
||||
@Inject PingListener(Provider<Match> matchProvider, ListingService listingService) {
|
||||
this.matchProvider = matchProvider;
|
||||
this.listingService = listingService;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
private void onPing(ServerListPingEvent event) {
|
||||
event.getExtra().put("pgm", new Info());
|
||||
}
|
||||
|
||||
private class Info {
|
||||
private class Map {
|
||||
final String name;
|
||||
final @Nullable String icon;
|
||||
|
||||
private Map(String name, String icon) {
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
final @Nullable String session = listingService.sessionDigest();
|
||||
final Map map;
|
||||
final int participants;
|
||||
final int observers;
|
||||
|
||||
Info() {
|
||||
final Match match = matchProvider.get();
|
||||
this.map = new Map(match.getMap().getName(),
|
||||
match.getMap().getThumbnailUri().orElse(null));
|
||||
this.participants = match.getParticipatingPlayers().size();
|
||||
this.observers = match.getObservingPlayers().size();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
@ -64,6 +65,10 @@ public class MapDefinition {
|
||||
return folder;
|
||||
}
|
||||
|
||||
public Optional<String> getThumbnailUri() {
|
||||
return getFolder().getThumbnailUri();
|
||||
}
|
||||
|
||||
public String getDottedPath() {
|
||||
return Joiner.on(".").join(getFolder().getRelativePath());
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import tc.oc.api.docs.AbstractModel;
|
||||
import tc.oc.api.docs.SemanticVersion;
|
||||
import tc.oc.api.docs.virtual.MapDoc;
|
||||
@ -105,7 +106,7 @@ public class MapDocument extends AbstractModel implements MapDoc {
|
||||
|
||||
@Override
|
||||
public Collection<String> images() {
|
||||
return folder.getThumbnails();
|
||||
return Collections2.transform(folder.getImages(), path -> path.getFileName().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,14 +9,18 @@ import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import tc.oc.commons.core.util.Lazy;
|
||||
import tc.oc.commons.core.util.Utils;
|
||||
import tc.oc.image.ImageUtils;
|
||||
|
||||
public class MapFolder {
|
||||
|
||||
public static final String MAP_DESCRIPTION_FILE_NAME = "map.xml";
|
||||
public static final String THUMBNAIL_FILE_NAME = "map.png";
|
||||
public static final String IMAGE_FILE_NAME = "map.png";
|
||||
public static final int THUMBNAIL_HEIGHT = 64;
|
||||
|
||||
public static boolean isMapFolder(Path path) {
|
||||
return Files.isDirectory(path) && Files.isRegularFile(path.resolve(MAP_DESCRIPTION_FILE_NAME));
|
||||
@ -24,7 +28,17 @@ public class MapFolder {
|
||||
|
||||
private final MapSource source;
|
||||
private final Path path;
|
||||
private Collection<String> thumbnails;
|
||||
|
||||
private final Lazy<Collection<Path>> images = Lazy.from(() -> {
|
||||
final Path path = getAbsolutePath().resolve(IMAGE_FILE_NAME);
|
||||
return Files.isRegularFile(path)? Collections.singleton(path)
|
||||
: Collections.emptySet();
|
||||
});
|
||||
|
||||
private final Lazy<Optional<String>> thumbnailUri = Lazy.from(
|
||||
() -> getImages().stream().findAny()
|
||||
.map(path -> ImageUtils.thumbnailUri(path, THUMBNAIL_HEIGHT))
|
||||
);
|
||||
|
||||
public MapFolder(MapSource source, Path path) {
|
||||
this.source = source;
|
||||
@ -111,14 +125,11 @@ public class MapFolder {
|
||||
return getRelativeUrl(getRelativeDescriptionFilePath());
|
||||
}
|
||||
|
||||
public Collection<String> getThumbnails() {
|
||||
if(thumbnails == null) {
|
||||
if(Files.isRegularFile(getAbsolutePath().resolve(THUMBNAIL_FILE_NAME))) {
|
||||
thumbnails = Collections.singleton(THUMBNAIL_FILE_NAME);
|
||||
} else {
|
||||
thumbnails = Collections.emptySet();
|
||||
}
|
||||
}
|
||||
return thumbnails;
|
||||
public Optional<String> getThumbnailUri() {
|
||||
return thumbnailUri.get();
|
||||
}
|
||||
|
||||
public Collection<Path> getImages() {
|
||||
return images.get();
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,21 @@
|
||||
# If false, PGM will load its classes but not enable itself
|
||||
enabled: true
|
||||
|
||||
# Public PGM listing service
|
||||
# --------------------------
|
||||
# If announce is enabled, and this server is running the PGM plugin,
|
||||
# the listing service will be notified whenever this server starts up
|
||||
# or shuts down. If the server is reachable at the announced address,
|
||||
# it will be included in the public list.
|
||||
#
|
||||
# WARNING: Enabling this will publish your IP address to the world,
|
||||
# unless you set server-host to something else.
|
||||
announce:
|
||||
enabled: false # Announce this server?
|
||||
# server-port: 25565 # Public port - defaults to whatever port is bound at startup
|
||||
# server-host: myserver.com # Public hostname or IP - if not set, the listing service will use
|
||||
# the IP address that the announcement originates from
|
||||
|
||||
# Variables accessible by the XML pre-processor
|
||||
environment:
|
||||
ranked: false
|
||||
|
@ -201,3 +201,7 @@ permissions:
|
||||
|
||||
pgm.destroyable.edit:
|
||||
description: Allows the player to edit the properties of destroyables
|
||||
|
||||
pgm.listing.announce:
|
||||
description: Allows the /announce command
|
||||
default: op
|
||||
|
@ -1,5 +1,7 @@
|
||||
package tc.oc.commons.core.configuration;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@ -15,6 +17,7 @@ import java.time.Duration;
|
||||
import tc.oc.commons.core.util.Predicates;
|
||||
import tc.oc.commons.core.util.TimeUtils;
|
||||
import tc.oc.minecraft.api.configuration.ConfigurationSection;
|
||||
import tc.oc.minecraft.api.configuration.InvalidConfigurationException;
|
||||
|
||||
public final class ConfigUtils {
|
||||
private ConfigUtils() {}
|
||||
@ -50,6 +53,26 @@ public final class ConfigUtils {
|
||||
return getDuration(section, path, null);
|
||||
}
|
||||
|
||||
public static URL needUrl(ConfigurationSection section, String path) {
|
||||
return parseUrl(section, path, section.needString(path), null);
|
||||
}
|
||||
|
||||
public static @Nullable URL getUrl(ConfigurationSection section, String path) {
|
||||
return getUrl(section, path, null);
|
||||
}
|
||||
|
||||
public static URL getUrl(ConfigurationSection section, String path, URL def) {
|
||||
return parseUrl(section, path, section.getString(path), def);
|
||||
}
|
||||
|
||||
private static URL parseUrl(ConfigurationSection section, String path, @Nullable String value, URL def) {
|
||||
try {
|
||||
return value == null ? def : new URL(value);
|
||||
} catch(MalformedURLException e) {
|
||||
throw new InvalidConfigurationException(section, path, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildDeepMap(Map<String, Object> map, ConfigurationSection section, String prefix) {
|
||||
for(String key : section.getKeys()) {
|
||||
final Object obj = section.get(key);
|
||||
|
49
Util/core/src/main/java/tc/oc/image/ImageUtils.java
Normal file
49
Util/core/src/main/java/tc/oc/image/ImageUtils.java
Normal file
@ -0,0 +1,49 @@
|
||||
package tc.oc.image;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import tc.oc.net.UriUtils;
|
||||
|
||||
public interface ImageUtils {
|
||||
|
||||
static BufferedImage resize(BufferedImage image, int width, int height) {
|
||||
final BufferedImage resized = new BufferedImage(width, 64, image.getType());
|
||||
final Graphics2D g = resized.createGraphics();
|
||||
g.addRenderingHints(ImmutableMap.of(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR));
|
||||
g.drawImage(image, 0, 0, width, height, null);
|
||||
g.dispose();
|
||||
return resized;
|
||||
}
|
||||
|
||||
static byte[] png(BufferedImage image) {
|
||||
try {
|
||||
final ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", data);
|
||||
return data.toByteArray();
|
||||
} catch(IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] thumbnail(Path path, int height) {
|
||||
try {
|
||||
final BufferedImage image = ImageIO.read(path.toFile());
|
||||
final BufferedImage thumb = resize(image, height * image.getWidth() / image.getHeight(), height);
|
||||
return png(thumb);
|
||||
} catch(IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
static String thumbnailUri(Path path, int height) {
|
||||
return UriUtils.dataUri("image/png", thumbnail(path, height));
|
||||
}
|
||||
}
|
20
Util/core/src/main/java/tc/oc/net/UriUtils.java
Normal file
20
Util/core/src/main/java/tc/oc/net/UriUtils.java
Normal file
@ -0,0 +1,20 @@
|
||||
package tc.oc.net;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Base64;
|
||||
|
||||
public interface UriUtils {
|
||||
|
||||
static String dataUri(String mime, byte[] data) {
|
||||
return "data:" + mime + ";base64," + Base64.getEncoder().encodeToString(data);
|
||||
}
|
||||
|
||||
static URL url(String spec) {
|
||||
try {
|
||||
return new URL(spec);
|
||||
} catch(MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Malformed URL: " + spec);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user