From cef9fa72c1aacda8b9c3d243057f51f54f80cbc1 Mon Sep 17 00:00:00 2001 From: Peijun Ma Date: Sun, 8 Oct 2023 01:30:39 -0400 Subject: [PATCH] Support twitch oath --- .../java/str_exporter/AuthHttpServer.java | 13 +--- .../java/str_exporter/BackendBroadcaster.java | 2 +- src/main/java/str_exporter/EBSClient.java | 43 +++++++++++++ .../java/str_exporter/JSONMessageBuilder.java | 10 ++- .../str_exporter/SlayTheRelicsExporter.java | 61 +++++++++++++------ src/main/java/str_exporter/User.java | 6 ++ src/main/resources/ModTheSpire.json | 2 +- 7 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 src/main/java/str_exporter/EBSClient.java create mode 100644 src/main/java/str_exporter/User.java diff --git a/src/main/java/str_exporter/AuthHttpServer.java b/src/main/java/str_exporter/AuthHttpServer.java index e68a258..c97dff1 100644 --- a/src/main/java/str_exporter/AuthHttpServer.java +++ b/src/main/java/str_exporter/AuthHttpServer.java @@ -9,15 +9,11 @@ import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; -import java.security.SecureRandom; -import java.util.Base64; public class AuthHttpServer implements HttpHandler { private String state; private String index; - private static final SecureRandom secureRandom = new SecureRandom(); - private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder(); private String token = ""; @@ -25,9 +21,9 @@ public class AuthHttpServer implements HttpHandler { private HttpServer server; - public AuthHttpServer() { + public AuthHttpServer(String state) { FileHandle fd = Gdx.files.internal("SlayTheRelicsExporter" + File.separator + "index.html"); - state = generateNewToken(); + this.state = state; index = fd.readString().replaceFirst("STATE", state); } @@ -42,11 +38,6 @@ public void stop() { server.stop(0); } - private static String generateNewToken() { - byte[] randomBytes = new byte[24]; - secureRandom.nextBytes(randomBytes); - return base64Encoder.encodeToString(randomBytes); - } @Override public void handle(HttpExchange httpExchange) throws IOException { diff --git a/src/main/java/str_exporter/BackendBroadcaster.java b/src/main/java/str_exporter/BackendBroadcaster.java index fc87d0b..410d3e8 100644 --- a/src/main/java/str_exporter/BackendBroadcaster.java +++ b/src/main/java/str_exporter/BackendBroadcaster.java @@ -104,7 +104,7 @@ private void broadcastMessage(String msg) { } private static BufferedReader makeRequest(String msg) throws IOException { - URL url = new URL(SlayTheRelicsExporter.getApiUrl()); + URL url = new URL(SlayTheRelicsExporter.getApiUrl() + "/api/v1/message"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Content-Type", "application/json"); //; utf-8 diff --git a/src/main/java/str_exporter/EBSClient.java b/src/main/java/str_exporter/EBSClient.java new file mode 100644 index 0000000..765221c --- /dev/null +++ b/src/main/java/str_exporter/EBSClient.java @@ -0,0 +1,43 @@ +package str_exporter; + + +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +public class EBSClient { + public EBSClient() { + } + + public static User verifyCredentials(String code) throws IOException { + Gson gson = new Gson(); + + URL url = new URL(SlayTheRelicsExporter.getApiUrl() + "/api/v1/auth"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json"); //; utf-8 + con.setRequestProperty("Accept", "application/json"); + con.setDoOutput(true); + + OutputStream os = con.getOutputStream(); + String msg = "{\"code\":\"" + code + "\"}"; + byte[] input = msg.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + + try (BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), + StandardCharsets.UTF_8))) { + if (con.getResponseCode() == 200) { + JsonReader reader = new JsonReader(br); + return gson.fromJson(reader, User.class); + } + throw new IOException("Failed : HTTP error code : " + con.getResponseCode()); + } + } +} diff --git a/src/main/java/str_exporter/JSONMessageBuilder.java b/src/main/java/str_exporter/JSONMessageBuilder.java index 978053e..86b909f 100644 --- a/src/main/java/str_exporter/JSONMessageBuilder.java +++ b/src/main/java/str_exporter/JSONMessageBuilder.java @@ -1,7 +1,5 @@ package str_exporter; -import com.megacrit.cardcrawl.core.CardCrawlGame; - public class JSONMessageBuilder { private String login, secret, version; @@ -14,6 +12,14 @@ public JSONMessageBuilder(String login, String secret, String version, int msg_t this.msg_type = msg_type; } + public void setSecret(String secret) { + this.secret = secret; + } + + public void setLogin(String login) { + this.login = login; + } + public String buildJson() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/str_exporter/SlayTheRelicsExporter.java b/src/main/java/str_exporter/SlayTheRelicsExporter.java index 9d41ceb..3ab6b3d 100644 --- a/src/main/java/str_exporter/SlayTheRelicsExporter.java +++ b/src/main/java/str_exporter/SlayTheRelicsExporter.java @@ -25,7 +25,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.Properties; @@ -42,8 +44,6 @@ public class SlayTheRelicsExporter implements RelicGetSubscriber, public static final Logger logger = LogManager.getLogger(SlayTheRelicsExporter.class.getName()); public static final String MODID = "SlayTheRelicsExporter"; - private static String login = null; - private static String secret = null; private static String version = ""; private long lastTipsCheck = System.currentTimeMillis(); @@ -72,9 +72,14 @@ public class SlayTheRelicsExporter implements RelicGetSubscriber, private static String apiUrl = ""; private static long lastOkayBroadcast = 0; private static final String OAUTH_SETTINGS = "oauth"; + private static final String USER_SETTINGS = "user"; + public static String oathToken = ""; + public static String user = ""; SpireConfig config; + private static final SecureRandom secureRandom = new SecureRandom(); + private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder(); public SlayTheRelicsExporter() { logger.info("Slay The Relics Exporter initialized!"); @@ -89,13 +94,14 @@ public SlayTheRelicsExporter() { config.load(); delay = config.getInt(DELAY_SETTINGS); oathToken = config.getString(OAUTH_SETTINGS); + user = config.getString(USER_SETTINGS); } catch (Exception e) { e.printStackTrace(); } } private static boolean areCredentialsValid() { - return login != null && secret != null; + return user != null && !user.isEmpty() && oathToken != null && !oathToken.isEmpty(); } public static String getVersion() { @@ -118,8 +124,6 @@ public static void initialize() { String data = new String(Files.readAllBytes(path)); List lines = Files.readAllLines(path); - login = lines.get(0).split(":")[1].toLowerCase().trim(); - secret = lines.get(1).split(":")[1].trim(); setApiUrl(lines); logger.info("slaytherelics_config.txt was succesfully loaded"); @@ -127,9 +131,6 @@ public static void initialize() { e.printStackTrace(); } - if (!areCredentialsValid()) { - logger.info("slaytherelics_config.txt wasn't loaded, check if it exists."); - } instance = new SlayTheRelicsExporter(); } @@ -172,8 +173,8 @@ private void broadcastDeck() { } - private String getOauthToken() { - AuthHttpServer serv = new AuthHttpServer(); + private User getOauthToken(String state) { + AuthHttpServer serv = new AuthHttpServer(state); try { serv.start(); } catch (IOException e) { @@ -199,8 +200,18 @@ private String getOauthToken() { } serv.stop(); - // TODO: verify the token is valid - return token; + try { + return EBSClient.verifyCredentials(token); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private static String generateNewToken() { + byte[] randomBytes = new byte[24]; + secureRandom.nextBytes(randomBytes); + return base64Encoder.encodeToString(randomBytes); } @Override @@ -208,9 +219,9 @@ public void receivePostInitialize() { tipsBroadcaster = new BackendBroadcaster(BROADCAST_CHECK_QUEUE_PERIOD_MILLIS, false); deckBroadcaster = new BackendBroadcaster(BROADCAST_CHECK_QUEUE_PERIOD_MILLIS, false); okayBroadcaster = new BackendBroadcaster(BROADCAST_CHECK_QUEUE_PERIOD_MILLIS, true); - tipsJsonBuilder = new TipsJSONBuilder(login, secret, version); - deckJsonBuilder = new DeckJSONBuilder(login, secret, version); - okayJsonBuilder = new JSONMessageBuilder(login, secret, version, 5); + tipsJsonBuilder = new TipsJSONBuilder(user, oathToken, version); + deckJsonBuilder = new DeckJSONBuilder(user, oathToken, version); + okayJsonBuilder = new JSONMessageBuilder(user, oathToken, version, 5); ModPanel settingsPanel = new ModPanel(); @@ -245,8 +256,21 @@ public void receivePostInitialize() { }); ModLabelButton oauthBtn = new ModLabelButton("Connect with Twitch", 800f, 480f, settingsPanel, (me) -> { - oathToken = getOauthToken(); + String state = generateNewToken(); + User user = getOauthToken(state); + if (user == null) { + return; + } + oathToken = user.token; + SlayTheRelicsExporter.user = user.user; config.setString(OAUTH_SETTINGS, oathToken); + config.setString(USER_SETTINGS, user.user); + tipsJsonBuilder.setSecret(oathToken); + tipsJsonBuilder.setLogin(user.user); + deckJsonBuilder.setSecret(oathToken); + deckJsonBuilder.setLogin(user.user); + okayJsonBuilder.setSecret(oathToken); + okayJsonBuilder.setLogin(user.user); try { config.save(); } catch (IOException e) { @@ -296,8 +320,9 @@ public void receivePostRender(SpriteBatch spriteBatch) { String okayMsg = okayJsonBuilder.buildJson(); lastOkayBroadcast = System.currentTimeMillis(); - okayBroadcaster.queueMessage(okayMsg); -// logger.info(okayMsg); + if (areCredentialsValid()) { + okayBroadcaster.queueMessage(okayMsg); + } } } diff --git a/src/main/java/str_exporter/User.java b/src/main/java/str_exporter/User.java new file mode 100644 index 0000000..62223ad --- /dev/null +++ b/src/main/java/str_exporter/User.java @@ -0,0 +1,6 @@ +package str_exporter; + +public class User { + public String user; + public String token; +} diff --git a/src/main/resources/ModTheSpire.json b/src/main/resources/ModTheSpire.json index f1ea480..a14a9f8 100644 --- a/src/main/resources/ModTheSpire.json +++ b/src/main/resources/ModTheSpire.json @@ -3,7 +3,7 @@ "name": "Slay the Relics Exporter", "author_list": ["LordAddy", "vmService"], "description": "This mod exports data to Slay the Relics Twitch extension. \n\nThis mod in combination with the extension displays deck view and tooltips for viewers on stream for relics, potions, player/monster powers, orbs, even some custom tooltips from some mods. \nThe viewers just need to hover over the respective item just as if they were in the game themselves.\n\nSee the extension config on Twitch for setup instructions (search 'Slay the Relics').", - "version": "1.2.5", + "version": "1.3.0", "sts_version": "07-30-2020", "mts_version": "3.15.0", "dependencies": ["basemod"]