Adds QR-code scanner to game
Java CI / build (push) Successful in 3m16s

Fixes notifications on non-screen elements
adds logging for resps from api SPWORLDS

Signed-off-by: Dmitrii <computer@yawaflua.tech>

Took 2 hours 15 minutes
This commit is contained in:
Dmitrii `yawaflua` Shimanskii
2026-04-03 00:47:14 +03:00
committed by Dmitrii
parent 3e9753055e
commit d49ba6907e
13 changed files with 400 additions and 57 deletions
+2
View File
@@ -50,6 +50,8 @@ dependencies {
}
include(implementation("org.xerial:sqlite-jdbc:3.46.1.3"))
include(clientImplementation("com.google.zxing:core:${project.zxing_version}"))
include(clientImplementation("com.google.zxing:javase:${project.zxing_version}"))
}
processResources {
+2 -1
View File
@@ -6,10 +6,11 @@ minecraft_version=1.21.11
yarn_mappings=1.21.11+build.4
loader_version=0.18.1
# Mod Properties
mod_version=0.1-pre-alpha
mod_version=0.2-pre-alpha
maven_group=git.yawaflua.tech
archives_base_name=SPMega
# Dependencies
# check this on https://modmuss50.me/fabric.html
fabric_version=0.141.1+1.21.11
cloth_config_version=21.11.153
zxing_version=3.5.3
@@ -1,17 +1,23 @@
package git.yawaflua.tech.spmega.client;
import git.yawaflua.tech.spmega.SPMega;
import git.yawaflua.tech.spmega.client.qr.QRCodeScanner;
import git.yawaflua.tech.spmega.client.ui.UiNotifications;
import git.yawaflua.tech.spmega.client.ui.UiOpeners;
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.player.UseEntityCallback;
import net.minecraft.block.entity.SignBlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.entity.decoration.ItemFrameEntity;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import org.lwjgl.glfw.GLFW;
@@ -22,6 +28,7 @@ import java.util.regex.Pattern;
public class SPMegaClient implements ClientModInitializer {
private static final Pattern ISOLATED_FIVE_DIGITS = Pattern.compile("(?<!\\d)(\\d{5})(?!\\d)");
private static KeyBinding openBankMenuKeyBinding;
private static KeyBinding scanQrKeyBinding;
private static String extractCardNumber(SignBlockEntity signBlockEntity) {
String candidate = findFiveDigits(signBlockEntity.getFrontText().getMessages(false));
@@ -31,16 +38,31 @@ public class SPMegaClient implements ClientModInitializer {
return findFiveDigits(signBlockEntity.getBackText().getMessages(false));
}
private static String extractCardNumber(ItemFrameEntity itemFrameEntity) {
if (itemFrameEntity.getHeldItemStack().isEmpty()) {
return null;
}
return findFiveDigits(itemFrameEntity.getHeldItemStack().getName().getString());
}
private static String findFiveDigits(Text[] lines) {
for (Text line : lines) {
Matcher matcher = ISOLATED_FIVE_DIGITS.matcher(line.getString());
if (matcher.find()) {
return matcher.group(1);
String candidate = findFiveDigits(line.getString());
if (candidate != null) {
return candidate;
}
}
return null;
}
private static String findFiveDigits(String text) {
Matcher matcher = ISOLATED_FIVE_DIGITS.matcher(text);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
@Override
public void onInitializeClient() {
openBankMenuKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
@@ -50,12 +72,47 @@ public class SPMegaClient implements ClientModInitializer {
KeyBinding.Category.GAMEPLAY
));
scanQrKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.spmega.scan_qr",
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_O,
KeyBinding.Category.GAMEPLAY
));
ClientTickEvents.END_CLIENT_TICK.register(client -> {
UiNotifications.instance().tick();
while (openBankMenuKeyBinding.wasPressed()) {
UiOpeners.openMainMenu(client);
}
while (scanQrKeyBinding.wasPressed()) {
QRCodeScanner.ScanQrCode(client);
}
});
// World HUD path (when no screen is open).
HudRenderCallback.EVENT.register((drawContext, tickDeltaManager) -> {
MinecraftClient client = MinecraftClient.getInstance();
if (client.currentScreen != null || client.textRenderer == null) {
return;
}
UiNotifications.instance().render(
drawContext,
client.textRenderer,
client.getWindow().getScaledWidth(),
client.getWindow().getScaledHeight()
);
});
// Screen path (for all GUI screens).
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) ->
ScreenEvents.afterRender(screen).register((screenInstance, drawContext, mouseX, mouseY, tickDelta) -> {
if (client.textRenderer == null) {
return;
}
UiNotifications.instance().render(drawContext, client.textRenderer, scaledWidth, scaledHeight);
})
);
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
if (client.player != null) {
BankUiService.instance().refreshOnServerJoin(client.player.getUuidAsString());
@@ -73,17 +130,42 @@ public class SPMegaClient implements ClientModInitializer {
return ActionResult.PASS;
}
if (!(world.getBlockEntity(hitResult.getBlockPos()) instanceof SignBlockEntity signBlockEntity)) {
if (world.getBlockEntity(hitResult.getBlockPos()) instanceof SignBlockEntity signBlockEntity) {
String cardNumber = extractCardNumber(signBlockEntity);
if (cardNumber == null) {
return ActionResult.PASS;
}
UiOpeners.openPaymentMenu(MinecraftClient.getInstance(), cardNumber);
return ActionResult.SUCCESS;
} else {
return ActionResult.PASS;
}
String cardNumber = extractCardNumber(signBlockEntity);
if (cardNumber == null) {
});
UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
if (!world.isClient()) {
return ActionResult.PASS;
}
if (!player.isSneaking()) {
return ActionResult.PASS;
}
if (SPMega.getConfig() == null || !SPMega.getConfig().signQuickPayEnabled()) {
return ActionResult.PASS;
}
if (entity instanceof ItemFrameEntity itemFrameEntity) {
String cardNumber = extractCardNumber(itemFrameEntity);
if (cardNumber == null) {
return ActionResult.PASS;
}
UiOpeners.openPaymentMenu(MinecraftClient.getInstance(), cardNumber);
return ActionResult.SUCCESS;
} else {
return ActionResult.PASS;
}
UiOpeners.openPaymentMenu(MinecraftClient.getInstance(), cardNumber);
return ActionResult.SUCCESS;
});
}
}
@@ -0,0 +1,131 @@
package git.yawaflua.tech.spmega.client.qr;
import com.google.zxing.*;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.multi.GenericMultipleBarcodeReader;
import git.yawaflua.tech.spmega.client.ui.QRcodeAcceptScreen;
import git.yawaflua.tech.spmega.client.ui.UiNotifications;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.texture.NativeImage;
import net.minecraft.client.util.ScreenshotRecorder;
import net.minecraft.text.Text;
import java.awt.image.BufferedImage;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
public class QRCodeScanner {
public static void ScanQrCode(MinecraftClient client) {
if (client == null || client.player == null || client.getFramebuffer() == null) {
return;
}
UiNotifications notifications = UiNotifications.instance();
int framebufferWidth = client.getWindow().getFramebufferWidth();
int framebufferHeight = client.getWindow().getFramebufferHeight();
if (framebufferWidth <= 0 || framebufferHeight <= 0) {
client.player.sendMessage(Text.translatable("message.spmega.qr.capture_failed"), false);
return;
}
try {
ScreenshotRecorder.takeScreenshot(client.getFramebuffer(), nativeImage -> {
try {
if (nativeImage == null || nativeImage.getWidth() <= 0 || nativeImage.getHeight() <= 0) {
client.execute(() -> {
if (client.player != null) {
notifications.show(Text.translatable("message.spmega.qr.capture_failed"));
}
});
return;
}
String result = decodeQRCode(nativeImageToBufferedImage(nativeImage));
client.execute(() -> {
if (client.player == null) {
return;
}
if (result == null) {
notifications.show(Text.translatable("message.spmega.qr.not_found"));
return;
}
Text clickableLink = Text.literal(result)
.styled(style -> style.withInsertion(result));
client.player.sendMessage(Text.translatable("message.spmega.qr.found_link", clickableLink), false);
client.setScreen(new QRcodeAcceptScreen(result, client.currentScreen));
});
} finally {
if (nativeImage != null) {
nativeImage.close();
}
}
});
} catch (Exception ex) {
notifications.show(Text.translatable("message.spmega.qr.error"));
}
}
private static BufferedImage nativeImageToBufferedImage(NativeImage screenshot) {
BufferedImage bufferedImage = new BufferedImage(
screenshot.getWidth(),
screenshot.getHeight(),
BufferedImage.TYPE_INT_RGB
);
for (int y = 0; y < screenshot.getHeight(); y++) {
for (int x = 0; x < screenshot.getWidth(); x++) {
int color = screenshot.getColorArgb(x, y);
bufferedImage.setRGB(x, y, color);
}
}
return bufferedImage;
}
private static String decodeQRCode(BufferedImage image) {
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, Collections.singletonList(BarcodeFormat.QR_CODE));
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
hints.put(DecodeHintType.ALSO_INVERTED, Boolean.TRUE);
LuminanceSource source = new RGBLuminanceSource(image.getWidth(), image.getHeight(), image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth()));
String result = tryDecodeWithStrategies(source, hints);
if (result == null) {
hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
result = tryDecodeWithStrategies(source, hints);
}
return result;
}
private static String tryDecodeWithStrategies(LuminanceSource source, Map<DecodeHintType, Object> hints) {
try {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
return new MultiFormatReader().decode(bitmap, hints).getText();
} catch (NotFoundException e) {
}
try {
BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
return new MultiFormatReader().decode(bitmap, hints).getText();
} catch (NotFoundException e) {
}
try {
GenericMultipleBarcodeReader reader = new GenericMultipleBarcodeReader(new MultiFormatReader());
Result[] results = reader.decodeMultiple(new BinaryBitmap(new HybridBinarizer(source)), hints);
if (results.length > 0) {
return results[0].getText();
}
} catch (NotFoundException e) {
}
return null;
}
}
@@ -67,7 +67,6 @@ public class AddCardScreen extends Screen {
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 24, 0xFFFFFF);
context.drawTextWithShadow(this.textRenderer, Text.literal("Card ID (UUID):"), centerX - 140, startY - 10, 0xCCCCCC);
context.drawTextWithShadow(this.textRenderer, Text.literal("Card Token:"), centerX - 140, startY + 18, 0xCCCCCC);
notifications.render(context, this.textRenderer, this.width, this.height);
}
private void submit() {
@@ -99,7 +99,6 @@ public class CardScreen extends Screen {
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 24, 0xFFFFFF);
context.drawTextWithShadow(this.textRenderer, Text.literal("Список карт"), leftX, startY - 18, 0xBFBFBF);
context.drawTextWithShadow(this.textRenderer, Text.literal("Действия"), rightX, startY - 18, 0xBFBFBF);
notifications.render(context, this.textRenderer, this.width, this.height);
}
private void updateCardButtonStates() {
@@ -1,11 +1,14 @@
package git.yawaflua.tech.spmega.client.ui;
import git.yawaflua.tech.spmega.client.qr.QRCodeScanner;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.text.Text;
import java.awt.*;
public class MainBankScreen extends Screen {
private final Screen parent;
@@ -34,6 +37,11 @@ public class MainBankScreen extends Screen {
this.addDrawableChild(ButtonWidget.builder(Text.literal("Закрыть"), button -> this.close())
.dimensions(startX + (buttonWidth + gap) * 2, y, buttonWidth, 20)
.build());
this.addDrawableChild(ButtonWidget.builder(Text.translatable("button.spmega.scan_qr"), button -> {
this.client.setScreen(null);
QRCodeScanner.ScanQrCode(this.client);
}).dimensions(centerX - 60, y + 28, 120, 20).build());
}
@Override
@@ -48,7 +56,7 @@ public class MainBankScreen extends Screen {
super.render(context, mouseX, mouseY, delta);
context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 24, 0xFFFFFF);
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal(greetingLine()), this.width / 2, 48, 0xFFD27F);
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal(greetingLine()), this.width / 2, 48, Color.WHITE.getRGB());
}
private String greetingLine() {
@@ -189,7 +189,6 @@ public class PaymentScreen extends Screen {
}
context.drawCenteredTextWithShadow(this.textRenderer, senderCardText, centerX, this.height - 20, 0xA9E5A9);
notifications.render(context, this.textRenderer, this.width, this.height);
}
private void submit() {
@@ -0,0 +1,70 @@
package git.yawaflua.tech.spmega.client.ui;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.text.Text;
import net.minecraft.util.Util;
import java.net.URI;
public class QRcodeAcceptScreen extends Screen {
private final String url;
private final Screen parent;
public QRcodeAcceptScreen(String url, Screen parent) {
super(Text.translatable("screen.spmega.qr.confirm_title"));
this.url = url;
this.parent = parent;
}
@Override
protected void init() {
this.addDrawableChild(ButtonWidget.builder(Text.translatable("button.spmega.qr.cancel"), button -> {
if (this.client != null) {
this.client.setScreen(parent);
}
}).dimensions(this.width / 2 - 155, this.height / 2 + 30, 150, 20).build());
this.addDrawableChild(ButtonWidget.builder(Text.translatable("button.spmega.qr.open_link"), button -> {
try {
Util.getOperatingSystem().open(new URI(url));
} catch (Exception exception) {
if (this.client != null && this.client.player != null) {
this.client.player.sendMessage(Text.translatable("message.spmega.qr.failed_open"), false);
}
}
if (this.client != null) {
this.client.setScreen(parent);
}
}).dimensions(this.width / 2 + 5, this.height / 2 + 30, 150, 20).build());
}
@Override
public void close() {
if (this.client != null) {
this.client.setScreen(parent);
}
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
context.drawCenteredTextWithShadow(
this.textRenderer,
Text.translatable("screen.spmega.qr.accept_link"),
this.width / 2,
this.height / 2 - 30,
0xFFFFFF
);
context.drawCenteredTextWithShadow(
this.textRenderer,
Text.literal(url),
this.width / 2,
this.height / 2 - 10,
0xFFD27F
);
}
}
@@ -12,11 +12,11 @@ import net.minecraft.text.Text;
import java.awt.*;
public final class UiNotifications {
private static final long DEFAULT_DURATION_MS = 3500L;
private static final int DEFAULT_DURATION_TICKS = 70;
private static final UiNotifications INSTANCE = new UiNotifications();
private Text currentText = Text.empty();
private long visibleUntilMs;
private int remainingTicks;
private UiNotifications() {
}
@@ -31,7 +31,6 @@ public final class UiNotifications {
}
try {
System.out.println(raw);
var reader = new JsonReader(new java.io.StringReader(raw));
reader.setStrictness(Strictness.LENIENT);
@@ -42,7 +41,6 @@ public final class UiNotifications {
}
} catch (Exception ignored) {
System.out.println(ignored.getMessage());
// fallback to raw text
}
@@ -54,7 +52,7 @@ public final class UiNotifications {
return;
}
currentText = text;
visibleUntilMs = System.currentTimeMillis() + DEFAULT_DURATION_MS;
remainingTicks = DEFAULT_DURATION_TICKS;
}
public synchronized void showMessage(String message) {
@@ -65,10 +63,7 @@ public final class UiNotifications {
}
public synchronized void render(DrawContext context, TextRenderer textRenderer, int width, int height) {
if (currentText == null || currentText.getString().isBlank()) {
return;
}
if (System.currentTimeMillis() > visibleUntilMs) {
if (!isVisible()) {
return;
}
@@ -83,5 +78,20 @@ public final class UiNotifications {
context.fill(x, y, x + boxWidth, y + boxHeight, Color.GRAY.getRGB());
context.drawTextWithShadow(textRenderer, currentText, x + padding, y + padding, Color.WHITE.getRGB());
}
public synchronized void tick() {
if (remainingTicks <= 0) {
return;
}
remainingTicks--;
if (remainingTicks <= 0) {
currentText = Text.empty();
remainingTicks = 0;
}
}
public synchronized boolean isVisible() {
return currentText != null && !currentText.getString().isBlank() && remainingTicks > 0;
}
}
@@ -1,5 +1,19 @@
{
"category.spmega": "SPMega",
"key.spmega.open_menu": "Open SPMega Menu"
"key.spmega.open_menu": "Open SPMega Menu",
"key.spmega.scan_qr": "Scan QR from Screen",
"button.spmega.scan_qr": "Scan QR",
"button.spmega.qr.cancel": "Cancel",
"button.spmega.qr.open_link": "Open Link",
"screen.spmega.qr.confirm_title": "Link confirmation",
"screen.spmega.qr.accept_link": "Open this link?",
"message.spmega.qr.capture_failed": "Failed to capture Minecraft screen",
"message.spmega.qr.not_found": "QR code not found on screen",
"message.spmega.qr.invalid_url": "QR does not contain a valid http/https URL",
"message.spmega.qr.opened": "Opened: %s",
"message.spmega.qr.error": "Failed to scan QR from screen",
"message.spmega.qr.hover_tip": "Click to open link",
"message.spmega.qr.found_link": "Found link: %s",
"message.spmega.qr.failed_open": "Failed to open link"
}
@@ -1,7 +1,7 @@
package git.yawaflua.tech.spmega;
public record ModConfig(String apiDomain, String apiToken, boolean signQuickPayEnabled) {
public static final String DEFAULT_API_DOMAIN = "https://spworlds.ru";
public static final String DEFAULT_API_DOMAIN = "https://spmega-api.yawaflua.tech";
public static final String DEFAULT_API_TOKEN = "ulBKE9MWEtIGiPAhXV69I28W9BRiSrV3";
public static final boolean DEFAULT_SIGN_QUICK_PAY_ENABLED = true;
@@ -51,52 +51,72 @@ public final class SPWorldsApiClient {
public CardInfo getCardInfo(CardAuth auth) throws IOException, InterruptedException {
HttpRequest request = requestBuilder("/api/public/card", auth).GET().build();
String body = send(request);
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
long balance = json.has("balance") ? json.get("balance").getAsLong() : 0L;
String webhook = json.has("webhook") && !json.get("webhook").isJsonNull()
? json.get("webhook").getAsString()
: "";
return new CardInfo(balance, webhook);
try {
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
long balance = json.has("balance") ? json.get("balance").getAsLong() : 0L;
String webhook = json.has("webhook") && !json.get("webhook").isJsonNull()
? json.get("webhook").getAsString()
: "";
return new CardInfo(balance, webhook);
} catch (Exception e) {
System.out.println("Failed to parse card info response: " + e.getMessage());
System.out.println(body);
throw new IOException("Failed to parse card info response", e);
}
}
public List<PlayerCard> getPlayerCards(String username, CardAuth auth) throws IOException, InterruptedException {
HttpRequest request = requestBuilder("/api/public/accounts/" + username + "/cards", auth).GET().build();
String body = send(request);
JsonArray json = JsonParser.parseString(body).getAsJsonArray();
try {
JsonArray json = JsonParser.parseString(body).getAsJsonArray();
List<PlayerCard> cards = new ArrayList<>();
for (JsonElement element : json) {
JsonObject card = element.getAsJsonObject();
String name = card.has("name") ? card.get("name").getAsString() : "";
String number = card.has("number") ? card.get("number").getAsString() : "";
cards.add(new PlayerCard(name, number));
List<PlayerCard> cards = new ArrayList<>();
for (JsonElement element : json) {
JsonObject card = element.getAsJsonObject();
String name = card.has("name") ? card.get("name").getAsString() : "";
String number = card.has("number") ? card.get("number").getAsString() : "";
cards.add(new PlayerCard(name, number));
}
return cards;
} catch (Exception e) {
System.out.println("Failed to parse player cards response: " + e.getMessage());
System.out.println(body);
throw new IOException("Failed to parse player cards response", e);
}
return cards;
}
public AccountMe getAccountMe(CardAuth auth) throws IOException, InterruptedException {
HttpRequest request = requestBuilder("/api/public/accounts/me", auth).GET().build();
String body = send(request);
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
try {
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
String id = json.has("id") ? json.get("id").getAsString() : "";
String username = json.has("username") ? json.get("username").getAsString() : "";
String minecraftUuid = json.has("minecraftUUID") ? json.get("minecraftUUID").getAsString() : "";
String id = json.has("id") ? json.get("id").getAsString() : "";
String username = json.has("username") ? json.get("username").getAsString() : "";
String minecraftUuid = json.has("minecraftUUID") ? json.get("minecraftUUID").getAsString() : "";
List<AccountCard> cards = new ArrayList<>();
if (json.has("cards") && json.get("cards").isJsonArray()) {
for (JsonElement element : json.getAsJsonArray("cards")) {
JsonObject card = element.getAsJsonObject();
cards.add(new AccountCard(
getString(card, "id"),
getString(card, "name"),
getString(card, "number"),
card.has("color") && !card.get("color").isJsonNull() ? card.get("color").getAsInt() : 0
));
List<AccountCard> cards = new ArrayList<>();
if (json.has("cards") && json.get("cards").isJsonArray()) {
for (JsonElement element : json.getAsJsonArray("cards")) {
JsonObject card = element.getAsJsonObject();
cards.add(new AccountCard(
getString(card, "id"),
getString(card, "name"),
getString(card, "number"),
card.has("color") && !card.get("color").isJsonNull() ? card.get("color").getAsInt() : 0
));
}
}
}
return new AccountMe(id, username, minecraftUuid, cards);
return new AccountMe(id, username, minecraftUuid, cards);
} catch (Exception e) {
System.out.println("Failed to parse account info response: " + e.getMessage());
System.out.println(body);
throw new IOException("Failed to parse account info response", e);
}
}
public TransactionResult createTransaction(CardAuth auth, String receiver, long amount, String comment)
@@ -111,9 +131,17 @@ public final class SPWorldsApiClient {
.build();
String body = send(request);
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
long balance = json.has("balance") ? json.get("balance").getAsLong() : 0L;
return new TransactionResult(balance);
try {
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
long balance = json.has("balance") ? json.get("balance").getAsLong() : 0L;
return new TransactionResult(balance);
} catch (Exception exception) {
System.out.println("Failed to parse transaction response: " + exception.getMessage());
System.out.println(body);
throw new IOException("Failed to parse transaction response", exception);
}
}
private HttpRequest.Builder requestBuilder(String path, CardAuth auth) {