package net.buycraft.plugin.sponge; import com.bugsnag.Bugsnag; import com.google.gson.JsonParseException; import com.google.inject.Inject; import lombok.Getter; import lombok.Setter; import net.buycraft.plugin.IBuycraftPlatform; import net.buycraft.plugin.client.ApiClient; import net.buycraft.plugin.client.ApiException; import net.buycraft.plugin.client.ProductionApiClient; import net.buycraft.plugin.data.responses.ServerInformation; import net.buycraft.plugin.execution.DuePlayerFetcher; import net.buycraft.plugin.execution.placeholder.NamePlaceholder; import net.buycraft.plugin.execution.placeholder.PlaceholderManager; import net.buycraft.plugin.execution.placeholder.UuidPlaceholder; import net.buycraft.plugin.execution.strategy.CommandExecutor; import net.buycraft.plugin.execution.strategy.PostCompletedCommandsTask; import net.buycraft.plugin.execution.strategy.QueuedCommandExecutor; import net.buycraft.plugin.shared.Setup; import net.buycraft.plugin.shared.config.BuycraftConfiguration; import net.buycraft.plugin.shared.config.BuycraftI18n; import net.buycraft.plugin.shared.config.signs.RecentPurchaseSignLayout; import net.buycraft.plugin.shared.config.signs.storage.RecentPurchaseSignStorage; import net.buycraft.plugin.shared.tasks.CouponUpdateTask; import net.buycraft.plugin.shared.tasks.ListingUpdateTask; import net.buycraft.plugin.shared.tasks.PlayerJoinCheckTask; import net.buycraft.plugin.shared.util.AnalyticsSend; import net.buycraft.plugin.sponge.command.*; import net.buycraft.plugin.sponge.logging.LoggerUtils; import net.buycraft.plugin.sponge.signs.purchases.RecentPurchaseSignListener; import net.buycraft.plugin.sponge.tasks.SignUpdater; import net.buycraft.plugin.sponge.util.VersionCheck; import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.args.GenericArguments; import org.spongepowered.api.command.spec.CommandSpec; import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.game.state.GamePreInitializationEvent; import org.spongepowered.api.event.game.state.GameStoppingServerEvent; import org.spongepowered.api.plugin.Plugin; import org.spongepowered.api.text.Text; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.concurrent.TimeUnit; @Plugin(id = "buycraft", name = "Buycraft", version = BuycraftPlugin.MAGIC_VERSION) public class BuycraftPlugin { static final String MAGIC_VERSION = "SET_BY_MAGIC"; @Getter private final PlaceholderManager placeholderManager = new PlaceholderManager(); @Getter private final BuycraftConfiguration configuration = new BuycraftConfiguration(); @Getter @Setter private ApiClient apiClient; @Getter private DuePlayerFetcher duePlayerFetcher; @Getter private ListingUpdateTask listingUpdateTask; @Getter private ServerInformation serverInformation; @Getter private RecentPurchaseSignStorage recentPurchaseSignStorage; @Getter private OkHttpClient httpClient; @Getter private IBuycraftPlatform platform; @Getter private CommandExecutor commandExecutor; @Getter @Inject private Logger logger; @Getter private LoggerUtils loggerUtils; @Getter @Inject @ConfigDir(sharedRoot = false) private Path baseDirectory; @Getter private RecentPurchaseSignLayout recentPurchaseSignLayout = RecentPurchaseSignLayout.DEFAULT; @Getter private BuycraftI18n i18n; private PostCompletedCommandsTask completedCommandsTask; @Getter private PlayerJoinCheckTask playerJoinCheckTask; @Getter private CouponUpdateTask couponUpdateTask; @Listener public void onGamePreInitializationEvent(GamePreInitializationEvent event) { platform = new SpongeBuycraftPlatform(this); try { try { Files.createDirectory(baseDirectory); } catch (FileAlreadyExistsException ignored) { } Path configPath = baseDirectory.resolve("config.properties"); try { configuration.load(configPath); } catch (NoSuchFileException e) { // Save defaults configuration.fillDefaults(); configuration.save(configPath); } } catch (IOException e) { getLogger().error("Unable to load configuration! The plugin will disable itself now.", e); return; } i18n = configuration.createI18n(); httpClient = Setup.okhttp(baseDirectory.resolve("cache").toFile()); // Check for latest version. String curVersion = getClass().getAnnotation(Plugin.class).version(); if (configuration.isCheckForUpdates()) { VersionCheck check = new VersionCheck(this, curVersion); try { check.verify(); } catch (IOException e) { getLogger().error("Can't check for updates", e); } Sponge.getEventManager().registerListeners(this, check); } String implVersion = Sponge.getPlatform().getImplementation().getVersion().orElse("UNKNOWN"); Bugsnag bugsnagClient = Setup.bugsnagClient(httpClient, "sponge", curVersion, implVersion, this::getServerInformation); loggerUtils = new LoggerUtils(this, bugsnagClient); String serverKey = configuration.getServerKey(); if (serverKey == null || serverKey.equals("INVALID")) { getLogger().info("Looks like this is a fresh setup. Get started by using 'buycraft secret <key>' in the console."); } else { getLogger().info("Validating your server key..."); ApiClient client = new ProductionApiClient(configuration.getServerKey(), httpClient); try { updateInformation(client); } catch (IOException | ApiException e) { getLogger().error(String.format("We can't check if your server can connect to Buycraft: %s", e.getMessage())); } apiClient = client; } placeholderManager.addPlaceholder(new NamePlaceholder()); placeholderManager.addPlaceholder(new UuidPlaceholder()); platform.executeAsyncLater(duePlayerFetcher = new DuePlayerFetcher(platform, configuration.isVerbose()), 1, TimeUnit.SECONDS); completedCommandsTask = new PostCompletedCommandsTask(platform); commandExecutor = new QueuedCommandExecutor(platform, completedCommandsTask); Sponge.getScheduler().createTaskBuilder().intervalTicks(1).delayTicks(1).execute((Runnable) commandExecutor).submit(this); Sponge.getScheduler().createTaskBuilder().intervalTicks(20).delayTicks(20).async().execute(completedCommandsTask).submit(this); playerJoinCheckTask = new PlayerJoinCheckTask(platform); Sponge.getScheduler().createTaskBuilder().intervalTicks(20).delayTicks(20).execute(playerJoinCheckTask).submit(this); listingUpdateTask = new ListingUpdateTask(platform, null); couponUpdateTask = new CouponUpdateTask(platform, null); if (apiClient != null) { getLogger().info("Fetching all server packages..."); listingUpdateTask.run(); couponUpdateTask.run(); } Sponge.getScheduler().createTaskBuilder().delayTicks(20 * 60 * 20).intervalTicks(20 * 60 * 20).execute(listingUpdateTask).async() .submit(this); Sponge.getScheduler().createTaskBuilder().delayTicks(20 * 60).intervalTicks(20 * 60).execute(couponUpdateTask).async() .submit(this); recentPurchaseSignStorage = new RecentPurchaseSignStorage(); try { recentPurchaseSignStorage.load(baseDirectory.resolve("purchase_signs.json")); } catch (IOException | JsonParseException e) { logger.warn("Can't load purchase signs, continuing anyway", e); } try { Path signLayoutDirectory = baseDirectory.resolve("sign_layouts"); try { Files.createDirectory(signLayoutDirectory); } catch (FileAlreadyExistsException ignored) { } Path rpPath = signLayoutDirectory.resolve("recentpurchase.txt"); try { Files.copy(getClass().getClassLoader().getResourceAsStream("sign_layouts/recentpurchase.txt"), rpPath); } catch (FileAlreadyExistsException ignored) { } recentPurchaseSignLayout = new RecentPurchaseSignLayout(Files.readAllLines(rpPath, StandardCharsets.UTF_8)); } catch (IOException e) { getLogger().error("Unable to load sign layouts", e); } Sponge.getScheduler().createTaskBuilder() .delay(1, TimeUnit.SECONDS) .interval(15, TimeUnit.MINUTES) .execute(new SignUpdater(this)) .submit(this); if (serverInformation != null) { Sponge.getScheduler().createTaskBuilder() .delay(0, TimeUnit.SECONDS) .interval(1, TimeUnit.DAYS) .execute(() -> { try { AnalyticsSend.postServerInformation(httpClient, configuration.getServerKey(), platform, Sponge.getServer().getOnlineMode()); } catch (IOException e) { getLogger().warn("Can't send analytics", e); } }) .submit(this); } Sponge.getEventManager().registerListeners(this, new BuycraftListener(this)); Sponge.getEventManager().registerListeners(this, new RecentPurchaseSignListener(this)); Sponge.getCommandManager().register(this, buildCommands(), "buycraft"); Sponge.getCommandManager().register(this, CommandSpec.builder() .description(Text.of(i18n.get("usage_sponge_listing"))) .executor(new ListPackagesCmd(this)) .build(), configuration.getBuyCommandName()); } @Listener public void onGameStoppingServerEvent(GameStoppingServerEvent event) { try { recentPurchaseSignStorage.save(baseDirectory.resolve("purchase_signs.json")); } catch (IOException e) { logger.error("Can't save purchase signs, continuing anyway"); } completedCommandsTask.flush(); } private CommandSpec buildCommands() { CommandSpec refresh = CommandSpec.builder() .description(Text.of(i18n.get("usage_refresh"))) .permission("buycraft.admin") .executor(new RefreshCmd(this)) .build(); CommandSpec secret = CommandSpec.builder() .description(Text.of(i18n.get("usage_secret"))) .permission("buycraft.admin") .arguments(GenericArguments.onlyOne(GenericArguments.string(Text.of("secret")))) .executor(new SecretCmd(this)) .build(); CommandSpec report = CommandSpec.builder() .description(Text.of(i18n.get("usage_report"))) .executor(new ReportCmd(this)) .permission("buycraft.admin") .build(); CommandSpec info = CommandSpec.builder() .description(Text.of(i18n.get("usage_information"))) .executor(new InfoCmd(this)) .build(); CommandSpec forcecheck = CommandSpec.builder() .description(Text.of(i18n.get("usage_forcecheck"))) .executor(new ForceCheckCmd(this)) .permission("buycraft.admin") .build(); CommandSpec coupon = buildCouponCommands(); return CommandSpec.builder() .description(Text.of("Main command for the Buycraft plugin.")) .child(report, "report") .child(secret, "secret") .child(refresh, "refresh") .child(info, "info") .child(forcecheck, "forcecheck") .child(coupon, "coupon") .build(); } private CommandSpec buildCouponCommands() { CouponCmd cmd = new CouponCmd(this); CommandSpec listing = CommandSpec.builder() .executor(cmd::listCoupons) .build(); CommandSpec create = CommandSpec.builder() .executor(cmd::createCoupon) .arguments(GenericArguments.allOf(GenericArguments.string(Text.of("args")))) .build(); CommandSpec delete = CommandSpec.builder() .executor(cmd::deleteCoupon) .arguments(GenericArguments.onlyOne(GenericArguments.string(Text.of("code")))) .build(); return CommandSpec.builder() .description(Text.of(i18n.get("usage_coupon"))) .permission("buycraft.admin") .child(listing, "list") .child(create, "create") .child(delete, "delete") .build(); } public void saveConfiguration() throws IOException { configuration.save(baseDirectory.resolve("config.properties")); } public void updateInformation(ApiClient client) throws IOException, ApiException { serverInformation = client.getServerInformation(); if (!configuration.isBungeeCord() && Sponge.getServer().getOnlineMode() != serverInformation.getAccount().isOnlineMode()) { getLogger().warn("Your server and webstore online mode settings are mismatched. Unless you are using" + " a proxy and server combination (such as BungeeCord/Spigot or LilyPad/Connect) that corrects UUIDs, then" + " you may experience issues with packages not applying."); getLogger().warn("If you have verified that your set up is correct, you can suppress this message by setting " + "is-bungeecord=true in your BuycraftX config.properties."); } } }