From 92cf434ff17e2aad27b727e1402e972ba8ab94b1 Mon Sep 17 00:00:00 2001 From: cfi Date: Tue, 11 Jun 2019 18:28:10 +0200 Subject: [PATCH 1/8] start refactor --- src/main/java/me/okx/twitchsync/Revoker.java | 32 +--- .../java/me/okx/twitchsync/TwitchServer.java | 1 + .../java/me/okx/twitchsync/Validator.java | 152 +++++++++++------- .../java/me/okx/twitchsync/data/Channel.java | 41 +++++ .../java/me/okx/twitchsync/data/Options.java | 12 ++ .../me/okx/twitchsync/data/StateWithId.java | 8 +- .../java/me/okx/twitchsync/data/Token.java | 4 + .../me/okx/twitchsync/util/SqlHelper.java | 74 ++++++--- src/main/resources/config.yml | 62 ++++--- 9 files changed, 243 insertions(+), 143 deletions(-) create mode 100644 src/main/java/me/okx/twitchsync/data/Channel.java create mode 100644 src/main/java/me/okx/twitchsync/data/Options.java diff --git a/src/main/java/me/okx/twitchsync/Revoker.java b/src/main/java/me/okx/twitchsync/Revoker.java index 0df1604..5aa32cd 100644 --- a/src/main/java/me/okx/twitchsync/Revoker.java +++ b/src/main/java/me/okx/twitchsync/Revoker.java @@ -22,44 +22,21 @@ public Revoker(TwitchSync plugin) { @Override public void run() { - plugin.getLogger().info("Revoking now."); + plugin.getLogger().info("Syncing user data now."); long start = System.currentTimeMillis(); Map tokens = plugin.getSqlHelper().getTokens().get(); checkTokens(tokens); long time = System.currentTimeMillis() - start; - plugin.getLogger().info("Finished revoking in " + time + "ms."); + plugin.getLogger().info("Finished Sync in " + time + "ms."); } private void checkTokens(Map tokens) { - for(Map.Entry entry : tokens.entrySet()) { - UUID uuid = entry.getKey(); - - Token token = entry.getValue(); - - AccessToken accessToken = refresh(token.getAccessToken()); - plugin.getSqlHelper().setToken(uuid, - token.getId(), - accessToken.getAccessToken(), - accessToken.getRefreshToken()); - - plugin.getSqlHelper().isFollowing(uuid).ifPresent(b -> { - if(b && check(plugin.getValidator().getFollowingState(token.getId(), accessToken))) { - revoke("follow", uuid); - plugin.getSqlHelper().setFollowing(uuid, false); - } - }); - plugin.getSqlHelper().isSubscribed(uuid).ifPresent(b -> { - // if subscribed and all states are not YES - if(b && check(plugin.getValidator().getSubscriptionState(token.getId(), accessToken))) { - revoke("subscribe", uuid); - plugin.getSqlHelper().setSubscribed(uuid, false); - } - }); - } + plugin.getValidator().sync(tokens); } + /* private void revoke(String type, UUID uuid) { ConfigurationSection section = plugin.getConfig().getConfigurationSection(type); OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); @@ -77,6 +54,7 @@ private void revoke(String type, UUID uuid) { } }); } + */ /** * Check if all states are not YES diff --git a/src/main/java/me/okx/twitchsync/TwitchServer.java b/src/main/java/me/okx/twitchsync/TwitchServer.java index 9772464..fbcc34e 100644 --- a/src/main/java/me/okx/twitchsync/TwitchServer.java +++ b/src/main/java/me/okx/twitchsync/TwitchServer.java @@ -41,6 +41,7 @@ public void start() throws IOException { String code = parameters.get("code"); if(state != null && code != null) { + plugin.getValidator().store(UUID.fromString(state), code); response = plugin.getValidator().sync(UUID.fromString(state), code); } } diff --git a/src/main/java/me/okx/twitchsync/Validator.java b/src/main/java/me/okx/twitchsync/Validator.java index 4c6e2a7..afe10b7 100644 --- a/src/main/java/me/okx/twitchsync/Validator.java +++ b/src/main/java/me/okx/twitchsync/Validator.java @@ -13,9 +13,7 @@ import com.nimbusds.oauth2.sdk.id.Issuer; import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator; -import me.okx.twitchsync.data.CheckState; -import me.okx.twitchsync.data.MessageWithId; -import me.okx.twitchsync.data.StateWithId; +import me.okx.twitchsync.data.*; import me.okx.twitchsync.data.json.AccessToken; import me.okx.twitchsync.data.json.ChannelObject; import me.okx.twitchsync.data.json.CheckError; @@ -29,6 +27,7 @@ import me.okx.twitchsync.events.PlayerSubscriptionEvent; import me.okx.twitchsync.util.WebUtil; import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import java.io.IOException; @@ -36,19 +35,16 @@ import java.io.Reader; import java.net.URL; import java.text.ParseException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import java.util.stream.Collectors; import java.util.stream.Stream; public class Validator { private Gson gson = new Gson(); - private Map channels; + private Map channels; private TwitchSync plugin; private Cache userStates; @@ -58,22 +54,30 @@ public Validator(TwitchSync plugin) { .build(); this.plugin = plugin; CompletableFuture.runAsync(() -> { - this.channels = getChannelIds(plugin.getConfig().getStringList("channel-names")); + List channelList = new ArrayList<>(); + ConfigurationSection channelConfig = plugin.getConfig().getConfigurationSection("channels"); + if (channelConfig != null) { + Set channelNames = channelConfig.getKeys(false); + for (String name : channelNames) { + channelList.add(channelConfig.getObject(name, Channel.class)); + } + } + this.channels = getChannelMap(channelList); }); } - public String getChannelName(int channelId) { + public Channel getChannel(int channelId) { return channels.get(channelId); } - private Map getChannelIds(List channelNames) { + private Map getChannelMap(List channels) { Map headers = new HashMap<>(); headers.put("Client-ID", plugin.getConfig().getString("client-id")); headers.put("Accept", "application/vnd.twitchtv.v5+json"); Reader reader = WebUtil.getURL("https://api.twitch.tv/kraken/users?login=" - + String.join(",", channelNames), headers); + + channels.stream().map(Channel::getName).collect(Collectors.joining(",")), headers); Users users = plugin.debug(gson.fromJson(reader, Users.class), "Users"); if (users.getTotal() == 0) { @@ -81,11 +85,11 @@ private Map getChannelIds(List channelNames) { return null; } - Map channels = new HashMap<>(); + Map channelMap = new HashMap<>(); for (User user : users.getUsers()) { - channels.put(user.getId(), user.getName()); + channelMap.put(user.getId(), channels.stream().filter(c -> c.getName().equals(user.getName())).findFirst().get()); } - return channels; + return channelMap; } public String createAuthenticationUrl(UUID user) { @@ -100,6 +104,7 @@ public String createAuthenticationUrl(UUID user) { "&state=" + uuid; } + /* public SyncResponse sync(UUID withState, String code) { plugin.debug("States: " + userStates.asMap()); @@ -168,16 +173,31 @@ public SyncResponse sync(UUID withState, String code) { return null; } } + */ + + private String getUserId(AccessToken token) throws IOException, ParseException, BadJOSEException, JOSEException { + + Issuer iss = new Issuer("https://id.twitch.tv/oauth2"); + ClientID clientID = new ClientID(plugin.getConfig().getString("client-id")); + JWSAlgorithm jwsAlg = JWSAlgorithm.RS256; + URL jwkSetURL = new URL("https://id.twitch.tv/oauth2/keys"); + + IDTokenValidator validator = new IDTokenValidator(iss, clientID, jwsAlg, jwkSetURL); + JWT idToken = JWTParser.parse(token.getIdToken()); + + IDTokenClaimsSet claims; + + claims = validator.validate(idToken, null); + return claims.getSubject().getValue(); + } private MessageWithId getSubscriptionMessage(String userId, AccessToken token) { try { - StateWithId subscriptionState = plugin.debug( - getSubscriptionState(userId, token).sorted().findFirst().get(), - "Subscribe state"); + Stream subscriptionState = getSubscriptionState(userId, token).sorted(); return new MessageWithId( - mapState(subscriptionState.getState(), SyncMessage.SUBSCRIPTION_SUCCESS), - subscriptionState.getId()); + mapState(subscriptionState.findFirst().get().getState(), SyncMessage.SUBSCRIPTION_SUCCESS), + subscriptionState.findFirst().get().getId()); } catch (Exception ex) { // TODO: More information based on error type plugin.debug(ex); @@ -225,36 +245,6 @@ private AccessToken getAccessToken(String code) { return gson.fromJson(reader, AccessToken.class); } - private String getUserId(AccessToken token) throws IOException, ParseException, BadJOSEException, JOSEException { - /*AccessTokenVerifier verifier = JwtVerifiers.accessTokenVerifierBuilder() - .setIssuer("https://id.twitch.tv/oauth2/keys") - .setConnectionTimeout(Duration.ofSeconds(2)) // defaults to 1000ms - .setReadTimeout(Duration.ofSeconds(1)) // defaults to 1000ms - .build(); - JwtVerifier verifier = new JwtHelper() - .setIssuerUrl("https://id.twitch.tv/oauth2") - .setConnectionTimeout(2000) - .setReadTimeout(2000) - .setClientId(plugin.getConfig().getString("client-id")) - .build(); - Jwt jwt = verifier.decode(token.getIdToken()); - plugin.debug(jwt.getClaims(), "claims"); - return (String) jwt.getClaims().get("sub");*/ - - Issuer iss = new Issuer("https://id.twitch.tv/oauth2"); - ClientID clientID = new ClientID(plugin.getConfig().getString("client-id")); - JWSAlgorithm jwsAlg = JWSAlgorithm.RS256; - URL jwkSetURL = new URL("https://id.twitch.tv/oauth2/keys"); - - IDTokenValidator validator = new IDTokenValidator(iss, clientID, jwsAlg, jwkSetURL); - JWT idToken = JWTParser.parse(token.getIdToken()); - - IDTokenClaimsSet claims; - - claims = validator.validate(idToken, null); - return claims.getSubject().getValue(); - } - public Stream getSubscriptionState(String userId, AccessToken token) { return getStates(userId, token, "subscriptions"); } @@ -269,31 +259,31 @@ private Stream getStates(String userId, AccessToken token, String t headers.put("Accept", "application/vnd.twitchtv.v5+json"); headers.put("Authorization", "OAuth " + token.getAccessToken()); - return channels.keySet().stream() - .map(channelId -> getIndividualState(headers, - "https://api.twitch.tv/kraken/users/" + userId + "/" + type + "/", channelId)) + return channels.entrySet().stream() + .map(entry -> getIndividualState(entry.getValue(), headers, + "https://api.twitch.tv/kraken/users/" + userId + "/" + type + "/", entry.getKey())) .peek(s -> plugin.debug(s, "Peek")); } - private StateWithId getIndividualState(Map headers, String url, int channelId) { + private StateWithId getIndividualState(Channel channel, Map headers, String url, int channelId) { InputStreamReader reader = WebUtil.getURL(url + channelId, headers); JsonElement json = gson.fromJson(reader, JsonElement.class); ChannelObject object = gson.fromJson(json, ChannelObject.class); if (object.isValid()) { - return new StateWithId(CheckState.YES, channelId); + return new StateWithId(CheckState.YES, channel, channelId); } // user is not subscribed CheckError error = gson.fromJson(json, CheckError.class); switch (error.getStatus()) { case 404: - return new StateWithId(CheckState.NO, channelId); + return new StateWithId(CheckState.NO, channel, channelId); case 422: - return new StateWithId(CheckState.UNPROCESSABLE, channelId); + return new StateWithId(CheckState.UNPROCESSABLE, channel, channelId); default: plugin.debug("Check state: " + error); - return new StateWithId(CheckState.ERROR, channelId); + return new StateWithId(CheckState.ERROR, channel, channelId); } } @@ -306,4 +296,48 @@ public AccessToken refreshToken(String refreshToken) { new HashMap<>(), "POST"); return gson.fromJson(reader, AccessToken.class); } + + public void store(UUID uuid, String code) { + try { + AccessToken token = getAccessToken(code); + plugin.getSqlHelper().setToken(uuid, getUserId(token), token); + } catch (IOException | ParseException | BadJOSEException | JOSEException ex) { + + } + } + + public CompletableFuture sync(Map users) { + return CompletableFuture.runAsync(() -> users.forEach(this::sync)); + } + + public CompletableFuture sync(UUID uuid) { + return CompletableFuture.runAsync(() -> { + Token token = plugin.getSqlHelper().getToken(uuid).orElse(null); + sync(uuid, token).join(); + }); + } + + public CompletableFuture sync(UUID uuid, Token token) { + return CompletableFuture.runAsync(() -> { + if (token == null && plugin.getConfig().getBoolean("sync-roles")) { + // todo: ensure user does not have subscriber roles + plugin.getServer().getConsoleSender().sendMessage("[TwitchSync] sync-roles is not supported."); + return; + } + + token.setAccessToken(refreshAndSaveToken(uuid, token)); + + Stream states = getStates(token.getId(), token.getAccessToken(), "subscriptions"); + // todo: compare with database + // todo: check each channel and sync roles + // todo: check total subscription count for upgrades + }); + } + + private AccessToken refreshAndSaveToken(UUID uuid, Token token) { + AccessToken refresh = refreshToken(token.getAccessToken().getRefreshToken()); + plugin.getSqlHelper().setToken(uuid, token.getId(), refresh); + return refresh; + } + } diff --git a/src/main/java/me/okx/twitchsync/data/Channel.java b/src/main/java/me/okx/twitchsync/data/Channel.java new file mode 100644 index 0000000..8fd3cc4 --- /dev/null +++ b/src/main/java/me/okx/twitchsync/data/Channel.java @@ -0,0 +1,41 @@ +package me.okx.twitchsync.data; + +public class Channel { + + String name; + Options subscribeOptions; + Options followOptions; + String rank; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Options getSubscribeOptions() { + return subscribeOptions; + } + + public void setSubscribeOptions(Options subscribeOptions) { + this.subscribeOptions = subscribeOptions; + } + + public Options getFollowOptions() { + return followOptions; + } + + public void setFollowOptions(Options followOptions) { + this.followOptions = followOptions; + } + + public String getRank() { + return rank; + } + + public void setRank(String rank) { + this.rank = rank; + } +} diff --git a/src/main/java/me/okx/twitchsync/data/Options.java b/src/main/java/me/okx/twitchsync/data/Options.java new file mode 100644 index 0000000..8b43772 --- /dev/null +++ b/src/main/java/me/okx/twitchsync/data/Options.java @@ -0,0 +1,12 @@ +package me.okx.twitchsync.data; + +import java.util.List; + +public class Options { + + Boolean enabled; + String rank; + List commands; + List revokeCommands; + +} diff --git a/src/main/java/me/okx/twitchsync/data/StateWithId.java b/src/main/java/me/okx/twitchsync/data/StateWithId.java index ef4dd09..5576018 100644 --- a/src/main/java/me/okx/twitchsync/data/StateWithId.java +++ b/src/main/java/me/okx/twitchsync/data/StateWithId.java @@ -3,10 +3,16 @@ public class StateWithId implements Comparable { private CheckState state; private int id; + private Channel channel; - public StateWithId(CheckState state, int id) { + public StateWithId(CheckState state, Channel channel, int id) { this.state = state; this.id = id; + this.channel = channel; + } + + public Channel getChannel() { + return channel; } public int getId() { diff --git a/src/main/java/me/okx/twitchsync/data/Token.java b/src/main/java/me/okx/twitchsync/data/Token.java index 6b89a63..5a7b598 100644 --- a/src/main/java/me/okx/twitchsync/data/Token.java +++ b/src/main/java/me/okx/twitchsync/data/Token.java @@ -18,4 +18,8 @@ public Token(String id, AccessToken accessToken) { this.id = id; this.accessToken = accessToken; } + + public void setAccessToken(AccessToken token) { + this.accessToken = token; + } } diff --git a/src/main/java/me/okx/twitchsync/util/SqlHelper.java b/src/main/java/me/okx/twitchsync/util/SqlHelper.java index 9c652b8..929ec17 100644 --- a/src/main/java/me/okx/twitchsync/util/SqlHelper.java +++ b/src/main/java/me/okx/twitchsync/util/SqlHelper.java @@ -1,9 +1,20 @@ package me.okx.twitchsync.util; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; +import com.nimbusds.oauth2.sdk.id.ClientID; +import com.nimbusds.oauth2.sdk.id.Issuer; +import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; +import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator; import me.okx.twitchsync.TwitchSync; import me.okx.twitchsync.data.Token; import me.okx.twitchsync.data.json.AccessToken; +import java.io.IOException; +import java.net.URL; import java.nio.file.Path; import java.sql.Connection; import java.sql.DriverManager; @@ -11,6 +22,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.text.ParseException; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -28,8 +40,8 @@ public SqlHelper(TwitchSync plugin) { connection = DriverManager.getConnection("jdbc:sqlite:" + db); Statement stmt = connection.createStatement(); - stmt.execute("CREATE TABLE IF NOT EXISTS subscribed (uuid VARCHAR(36) PRIMARY KEY)"); - stmt.execute("CREATE TABLE IF NOT EXISTS following (uuid VARCHAR(36) PRIMARY KEY)"); + stmt.execute("CREATE TABLE IF NOT EXISTS subscribed (uuid VARCHAR(36), channel VARCHAR(50), PRIMARY KEY (uuid, channel))"); + stmt.execute("CREATE TABLE IF NOT EXISTS following (uuid VARCHAR(36), channel VARCHAR(50), PRIMARY KEY (uuid, channel))"); stmt.execute("CREATE TABLE IF NOT EXISTS tokens " + "(uuid VARCHAR(36) PRIMARY KEY, " + "id VARCHAR(12), " + @@ -64,18 +76,19 @@ private Optional getCount(String table) { } } - public Optional isFollowing(UUID uuid) { - return isInTable(uuid, "following"); + public Optional isFollowing(UUID uuid, String channel) { + return isInTable("following", uuid, channel); } - public Optional isSubscribed(UUID uuid) { - return isInTable(uuid, "subscribed"); + public Optional isSubscribed(UUID uuid, String channel) { + return isInTable("subscribed", uuid, channel); } - private Optional isInTable(UUID uuid, String table) { + private Optional isInTable(String table, UUID uuid, String channel) { try(PreparedStatement stmt = connection.prepareStatement( - "SELECT * FROM " + table + " WHERE uuid=?")) { + "SELECT * FROM " + table + " WHERE uuid = ? AND channel = ?")) { stmt.setString(1, uuid.toString()); + stmt.setString(2, channel); stmt.execute(); return Optional.of(stmt.getResultSet().next()); } catch (SQLException e) { @@ -84,14 +97,14 @@ private Optional isInTable(UUID uuid, String table) { } } - public void setToken(UUID uuid, String id, String accessToken, String refeshToken) { + public void setToken(UUID uuid, String userId, AccessToken token) { CompletableFuture.runAsync(() -> { try(PreparedStatement stmt = connection.prepareStatement( "REPLACE INTO tokens (uuid, id, access_token, refresh_token) VALUES (?, ?, ?, ?)")) { stmt.setString(1, uuid.toString()); - stmt.setString(2, id); - stmt.setString(3, accessToken); - stmt.setString(4, refeshToken); + stmt.setString(2, userId); + stmt.setString(3, token.getAccessToken()); + stmt.setString(4, token.getRefreshToken()); stmt.execute(); } catch (SQLException e) { e.printStackTrace(); @@ -118,40 +131,42 @@ public Optional> getTokens() { } } - public void setFollowing(UUID uuid, boolean following) { + public void setFollowing(UUID uuid, String channel, boolean following) { CompletableFuture.runAsync(() -> { if (following) { - addToTable(uuid, "following"); + addToTable("following", uuid, channel); } else { - deleteFromTable(uuid, "following"); + deleteFromTable("following", uuid, channel); } }); } - public void setSubscribed(UUID uuid, boolean subscribed) { + public void setSubscribed(UUID uuid, String channel, boolean subscribed) { CompletableFuture.runAsync(() -> { if (subscribed) { - addToTable(uuid, "subscribed"); + addToTable("subscribed", uuid, channel); } else { - deleteFromTable(uuid, "subscribed"); + deleteFromTable("subscribed", uuid, channel); } }); } - private void addToTable(UUID uuid, String database) { + private void addToTable(String table, UUID uuid, String channel) { try(PreparedStatement stmt = connection.prepareStatement( - "INSERT INTO " + database + " (uuid) VALUES (?)")) { + "INSERT INTO " + table + " (uuid, channel) VALUES (?, ?)")) { stmt.setString(1, uuid.toString()); + stmt.setString(2, channel); stmt.execute(); } catch (SQLException e) { e.printStackTrace(); } } - private void deleteFromTable(UUID uuid, String database) { + private void deleteFromTable(String table, UUID uuid, String channel) { try(PreparedStatement stmt = connection.prepareStatement( - "DELETE FROM " + database + " WHERE uuid=?")) { + "DELETE FROM " + table + " WHERE uuid = ? AND channel = ?")) { stmt.setString(1, uuid.toString()); + stmt.setString(2, channel); stmt.execute(); } catch (SQLException e) { e.printStackTrace(); @@ -165,4 +180,19 @@ public void close() { e.printStackTrace(); } } + + public Optional getToken(UUID uuid) { + try(PreparedStatement stmt = connection.prepareStatement("SELECT * FROM tokens WHERE uuid = ?")) { + stmt.setString(1, uuid.toString()); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + Token token = new Token(rs.getString("id"), + new AccessToken(rs.getString("access_token"), rs.getString("refresh_token"))); + return Optional.of(token); + } + return Optional.empty(); + } catch (SQLException ex) { + return Optional.empty(); + } + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3a9d9de..c4088b3 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,6 +1,30 @@ # The channel name(s) to check for subscription/follows. -channel-names: -- required +channels: + - name: kixstar + subscribe: + enabled: true + rank: kixsub + commands: + - "broadcast %name% has subscribed to %channel%!" + revoke-commands: [] + follow: + enabled: false + - name: thegodlynoob + subscribe: + enabled: true + rank: godlysub + commands: + - "broadcast %name% has subscribed to %channel%!" + revoke-commands: [] + follow: + enabled: false + +upgrades: + - rank: ubersub + threshold: 2 + +# remove roles from users that got subscription roles manually +sync-roles: true # The client ID given by Twitch. client-id: required @@ -21,7 +45,7 @@ expiry-time: 10 messages: subscription: no-subscription-program: "The channel does not have a subscription program set up." - success: "You have successfully received the Twitch subscription rewards." + success: "Successfully set up Twitch Sync." follow: success: "You have successfully received the Twitch following rewards." success-both: "You have received both the following and subscription rewards." @@ -29,7 +53,7 @@ messages: # If you have subscription or following rewards disabled you might want to change this # to say "You are not following" or "You are not subscribed" to make it clear # that it's only possible to get rewards for one thing - not-both: "You are neither subscribed or are following the channel." + not-both: "You are not subscribed to any of the channels." already-done: "You have already redeemed subscription and/or follow rewards." state-not-found: "This link has either expired, or already been used." @@ -41,36 +65,6 @@ messages: # this will remove the rank and run revoke-commands revoke-interval-days: 1 -# What to do when somebody subscribes -subscribe: - enabled: true - - # Rank to give a player. Only used if Vault and a permissions plugin is installed. - # Set to 'none' to disable. - rank: 'Subscriber' - - # Commands to execute. - # You can use the following placeholders: - # %name% - name of the player who subscribed - # %channel% - channel their subscription is registered to - # %channelid% - channel id of %channel% - commands: - - 'broadcast %name% has subscribed!' - - 'give %name% diamond 1' - - # commands to run when a player is no longer subscribed - # this only supports the %name% placeholder - revoke-commands: - - 'give %name% dirt 1' -# What to do when somebody follows -follow: - enabled: true - rank: 'Follower' - commands: - - 'broadcast %name% has followed the channel!' - - revoke-commands: - - 'broadcast %name% has unfollowed :(' # Enable debugging information. # If you encounter an unexpected error please enable this # and send the results to the dev! From 1af33847f9f010a6dc14d7fbc50ea7b3943e66b9 Mon Sep 17 00:00:00 2001 From: cfi Date: Tue, 11 Jun 2019 20:26:11 +0200 Subject: [PATCH 2/8] continue refactor --- .../java/me/okx/twitchsync/TwitchServer.java | 5 +- .../java/me/okx/twitchsync/Validator.java | 102 +++++++----------- .../java/me/okx/twitchsync/data/Channel.java | 14 +-- .../java/me/okx/twitchsync/data/Options.java | 31 ++++++ .../me/okx/twitchsync/util/SqlHelper.java | 79 ++++---------- src/main/resources/config.yml | 3 - 6 files changed, 94 insertions(+), 140 deletions(-) diff --git a/src/main/java/me/okx/twitchsync/TwitchServer.java b/src/main/java/me/okx/twitchsync/TwitchServer.java index fbcc34e..fedd1b7 100644 --- a/src/main/java/me/okx/twitchsync/TwitchServer.java +++ b/src/main/java/me/okx/twitchsync/TwitchServer.java @@ -1,6 +1,7 @@ package me.okx.twitchsync; import com.sun.net.httpserver.HttpServer; +import me.okx.twitchsync.data.Token; import me.okx.twitchsync.data.sync.SyncMessage; import me.okx.twitchsync.data.sync.SyncResponse; import me.okx.twitchsync.data.sync.SyncResponseFailure; @@ -41,8 +42,8 @@ public void start() throws IOException { String code = parameters.get("code"); if(state != null && code != null) { - plugin.getValidator().store(UUID.fromString(state), code); - response = plugin.getValidator().sync(UUID.fromString(state), code); + Token token = plugin.getValidator().store(UUID.fromString(state), code).get(); + response = plugin.getValidator().sync(UUID.fromString(state), token).get(); } } diff --git a/src/main/java/me/okx/twitchsync/Validator.java b/src/main/java/me/okx/twitchsync/Validator.java index afe10b7..79b3b75 100644 --- a/src/main/java/me/okx/twitchsync/Validator.java +++ b/src/main/java/me/okx/twitchsync/Validator.java @@ -25,8 +25,11 @@ import me.okx.twitchsync.data.sync.SyncResponseSuccess; import me.okx.twitchsync.events.PlayerFollowEvent; import me.okx.twitchsync.events.PlayerSubscriptionEvent; +import me.okx.twitchsync.util.SqlHelper; import me.okx.twitchsync.util.WebUtil; +import net.milkbowl.vault.permission.Permission; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; @@ -191,49 +194,6 @@ private String getUserId(AccessToken token) throws IOException, ParseException, return claims.getSubject().getValue(); } - private MessageWithId getSubscriptionMessage(String userId, AccessToken token) { - try { - Stream subscriptionState = getSubscriptionState(userId, token).sorted(); - - return new MessageWithId( - mapState(subscriptionState.findFirst().get().getState(), SyncMessage.SUBSCRIPTION_SUCCESS), - subscriptionState.findFirst().get().getId()); - } catch (Exception ex) { - // TODO: More information based on error type - plugin.debug(ex); - } - return new MessageWithId(SyncMessage.UNKNOWN_ERROR); - } - - private MessageWithId getFollowingMessage(String userId, AccessToken token) { - try { - StateWithId followingState = plugin.debug( - getFollowingState(userId, token).sorted().findFirst().get(), - "Follow state"); - - return new MessageWithId( - mapState(followingState.getState(), SyncMessage.FOLLOW_SUCCESS), - followingState.getId()); - } catch (Exception ex) { - // TODO: More information based on error type - plugin.debug(ex); - } - return new MessageWithId(SyncMessage.UNKNOWN_ERROR); - } - - private SyncMessage mapState(CheckState state, SyncMessage success) { - switch (state) { - case YES: - return success; - case NO: - return SyncMessage.NOT_BOTH; - case UNPROCESSABLE: - return SyncMessage.NO_SUBSCRIPTION_PROGRAM; - default: - return null; - } - } - private AccessToken getAccessToken(String code) { Reader reader = WebUtil.getURL("https://id.twitch.tv/oauth2/token" + "?client_id=" + plugin.getConfig().getString("client-id") + @@ -245,15 +205,8 @@ private AccessToken getAccessToken(String code) { return gson.fromJson(reader, AccessToken.class); } - public Stream getSubscriptionState(String userId, AccessToken token) { - return getStates(userId, token, "subscriptions"); - } - - public Stream getFollowingState(String userId, AccessToken token) { - return getStates(userId, token, "follows/channels"); - } - private Stream getStates(String userId, AccessToken token, String type) { + private Stream getStates(Token token, Map channels, String type) { Map headers = new HashMap<>(); headers.put("Client-ID", plugin.getConfig().getString("client-id")); headers.put("Accept", "application/vnd.twitchtv.v5+json"); @@ -261,7 +214,7 @@ private Stream getStates(String userId, AccessToken token, String t return channels.entrySet().stream() .map(entry -> getIndividualState(entry.getValue(), headers, - "https://api.twitch.tv/kraken/users/" + userId + "/" + type + "/", entry.getKey())) + "https://api.twitch.tv/kraken/users/" + token.getId() + "/" + type + "/", entry.getKey())) .peek(s -> plugin.debug(s, "Peek")); } @@ -297,12 +250,12 @@ public AccessToken refreshToken(String refreshToken) { return gson.fromJson(reader, AccessToken.class); } - public void store(UUID uuid, String code) { + public CompletableFuture store(UUID uuid, String code) { try { AccessToken token = getAccessToken(code); - plugin.getSqlHelper().setToken(uuid, getUserId(token), token); + return plugin.getSqlHelper().setToken(uuid, getUserId(token), token); } catch (IOException | ParseException | BadJOSEException | JOSEException ex) { - + return CompletableFuture.completedFuture(null); } } @@ -311,26 +264,43 @@ public CompletableFuture sync(Map users) { } public CompletableFuture sync(UUID uuid) { - return CompletableFuture.runAsync(() -> { - Token token = plugin.getSqlHelper().getToken(uuid).orElse(null); - sync(uuid, token).join(); - }); + return CompletableFuture.runAsync(() -> plugin.getSqlHelper().getToken(uuid).ifPresent(token -> sync(uuid, token).join())); } - public CompletableFuture sync(UUID uuid, Token token) { - return CompletableFuture.runAsync(() -> { - if (token == null && plugin.getConfig().getBoolean("sync-roles")) { - // todo: ensure user does not have subscriber roles - plugin.getServer().getConsoleSender().sendMessage("[TwitchSync] sync-roles is not supported."); - return; + public CompletableFuture sync(UUID uuid, Token token) { + return CompletableFuture.supplyAsync(() -> { + if (token == null) { + return null; } token.setAccessToken(refreshAndSaveToken(uuid, token)); - Stream states = getStates(token.getId(), token.getAccessToken(), "subscriptions"); + Stream states = getStates(token, channels, "subscriptions"); + SqlHelper helper = plugin.getSqlHelper(); + Permission perms = plugin.getPerms(); + states.forEach(state -> { + Boolean subscribed = null; + if (state.getState() == CheckState.YES) subscribed = true; + else if (state.getState() == CheckState.NO) subscribed = false; + if (subscribed != null) { + helper.setSubscribed(uuid, state.getChannel().getName(), subscribed); + OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); + String rank = state.getChannel().getSubscribeOptions().getRank(); + if (subscribed) { + if (!perms.playerInGroup(null, player, rank)) { + perms.playerAddGroup(null, player, rank); + } + } else { + if (perms.playerInGroup(null, player, rank)) { + perms.playerRemoveGroup(null, player, rank); + } + } + } + }); // todo: compare with database // todo: check each channel and sync roles // todo: check total subscription count for upgrades + return null; }); } diff --git a/src/main/java/me/okx/twitchsync/data/Channel.java b/src/main/java/me/okx/twitchsync/data/Channel.java index 8fd3cc4..fd8cef3 100644 --- a/src/main/java/me/okx/twitchsync/data/Channel.java +++ b/src/main/java/me/okx/twitchsync/data/Channel.java @@ -2,10 +2,9 @@ public class Channel { - String name; - Options subscribeOptions; - Options followOptions; - String rank; + private String name; + private Options subscribeOptions; + private Options followOptions; public String getName() { return name; @@ -31,11 +30,4 @@ public void setFollowOptions(Options followOptions) { this.followOptions = followOptions; } - public String getRank() { - return rank; - } - - public void setRank(String rank) { - this.rank = rank; - } } diff --git a/src/main/java/me/okx/twitchsync/data/Options.java b/src/main/java/me/okx/twitchsync/data/Options.java index 8b43772..6e8c40d 100644 --- a/src/main/java/me/okx/twitchsync/data/Options.java +++ b/src/main/java/me/okx/twitchsync/data/Options.java @@ -9,4 +9,35 @@ public class Options { List commands; List revokeCommands; + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public String getRank() { + return rank; + } + + public void setRank(String rank) { + this.rank = rank; + } + + public List getCommands() { + return commands; + } + + public void setCommands(List commands) { + this.commands = commands; + } + + public List getRevokeCommands() { + return revokeCommands; + } + + public void setRevokeCommands(List revokeCommands) { + this.revokeCommands = revokeCommands; + } } diff --git a/src/main/java/me/okx/twitchsync/util/SqlHelper.java b/src/main/java/me/okx/twitchsync/util/SqlHelper.java index 929ec17..410372d 100644 --- a/src/main/java/me/okx/twitchsync/util/SqlHelper.java +++ b/src/main/java/me/okx/twitchsync/util/SqlHelper.java @@ -1,28 +1,11 @@ package me.okx.twitchsync.util; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.proc.BadJOSEException; -import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.JWTParser; -import com.nimbusds.oauth2.sdk.id.ClientID; -import com.nimbusds.oauth2.sdk.id.Issuer; -import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; -import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator; import me.okx.twitchsync.TwitchSync; import me.okx.twitchsync.data.Token; import me.okx.twitchsync.data.json.AccessToken; -import java.io.IOException; -import java.net.URL; import java.nio.file.Path; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.text.ParseException; +import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -40,8 +23,9 @@ public SqlHelper(TwitchSync plugin) { connection = DriverManager.getConnection("jdbc:sqlite:" + db); Statement stmt = connection.createStatement(); - stmt.execute("CREATE TABLE IF NOT EXISTS subscribed (uuid VARCHAR(36), channel VARCHAR(50), PRIMARY KEY (uuid, channel))"); - stmt.execute("CREATE TABLE IF NOT EXISTS following (uuid VARCHAR(36), channel VARCHAR(50), PRIMARY KEY (uuid, channel))"); + stmt.execute("CREATE TABLE IF NOT EXISTS subscribed (uuid VARCHAR(36), channel VARCHAR(25), PRIMARY KEY (uuid, channel))"); + stmt.execute("CREATE TABLE IF NOT EXISTS following (uuid VARCHAR(36), channel VARCHAR(25), PRIMARY KEY (uuid, channel))"); + stmt.execute("CREATE TABLE IF NOT EXISTS credentials (userId NVARCHAR PRIMARY KEY, identProvider NVARCHAR)"); stmt.execute("CREATE TABLE IF NOT EXISTS tokens " + "(uuid VARCHAR(36) PRIMARY KEY, " + "id VARCHAR(12), " + @@ -53,72 +37,51 @@ public SqlHelper(TwitchSync plugin) { }); } - public Optional getFollowing() { - return getCount("following"); - } - - public Optional getSubscribed() { - return getCount("subscribed"); - } - - private Optional getCount(String table) { - try(PreparedStatement stmt = connection.prepareStatement( - "SELECT * FROM " + table)) { - ResultSet rs = stmt.executeQuery(); - int count = 0; - while(rs.next()) { - count++; - } - return Optional.of(count); - } catch (SQLException e) { - e.printStackTrace(); - return Optional.empty(); - } - } - - public Optional isFollowing(UUID uuid, String channel) { + public Boolean isFollowing(UUID uuid, String channel) { return isInTable("following", uuid, channel); } - public Optional isSubscribed(UUID uuid, String channel) { + public Boolean isSubscribed(UUID uuid, String channel) { return isInTable("subscribed", uuid, channel); } - private Optional isInTable(String table, UUID uuid, String channel) { - try(PreparedStatement stmt = connection.prepareStatement( + private Boolean isInTable(String table, UUID uuid, String channel) { + try (PreparedStatement stmt = connection.prepareStatement( "SELECT * FROM " + table + " WHERE uuid = ? AND channel = ?")) { stmt.setString(1, uuid.toString()); stmt.setString(2, channel); stmt.execute(); - return Optional.of(stmt.getResultSet().next()); + return stmt.getResultSet().next(); } catch (SQLException e) { e.printStackTrace(); - return Optional.empty(); + return false; } } - public void setToken(UUID uuid, String userId, AccessToken token) { - CompletableFuture.runAsync(() -> { - try(PreparedStatement stmt = connection.prepareStatement( + public CompletableFuture setToken(UUID uuid, String userId, AccessToken token) { + return CompletableFuture.supplyAsync(() -> { + try (PreparedStatement stmt = connection.prepareStatement( "REPLACE INTO tokens (uuid, id, access_token, refresh_token) VALUES (?, ?, ?, ?)")) { stmt.setString(1, uuid.toString()); stmt.setString(2, userId); stmt.setString(3, token.getAccessToken()); stmt.setString(4, token.getRefreshToken()); stmt.execute(); + return new Token(userId, token); } catch (SQLException e) { e.printStackTrace(); + return null; } }); } public Optional> getTokens() { - try(PreparedStatement stmt = connection.prepareStatement("SELECT * FROM tokens")) { + try (PreparedStatement stmt = connection.prepareStatement("SELECT * FROM tokens")) { ResultSet rs = stmt.executeQuery(); Map tokens = new HashMap<>(); - while(rs.next()) { + while (rs.next()) { tokens.put(UUID.fromString(rs.getString("uuid")), new Token(rs.getString("id"), new AccessToken(rs.getString("access_token"), rs.getString("refresh_token")))); @@ -152,7 +115,7 @@ public void setSubscribed(UUID uuid, String channel, boolean subscribed) { } private void addToTable(String table, UUID uuid, String channel) { - try(PreparedStatement stmt = connection.prepareStatement( + try (PreparedStatement stmt = connection.prepareStatement( "INSERT INTO " + table + " (uuid, channel) VALUES (?, ?)")) { stmt.setString(1, uuid.toString()); stmt.setString(2, channel); @@ -163,7 +126,7 @@ private void addToTable(String table, UUID uuid, String channel) { } private void deleteFromTable(String table, UUID uuid, String channel) { - try(PreparedStatement stmt = connection.prepareStatement( + try (PreparedStatement stmt = connection.prepareStatement( "DELETE FROM " + table + " WHERE uuid = ? AND channel = ?")) { stmt.setString(1, uuid.toString()); stmt.setString(2, channel); @@ -182,12 +145,12 @@ public void close() { } public Optional getToken(UUID uuid) { - try(PreparedStatement stmt = connection.prepareStatement("SELECT * FROM tokens WHERE uuid = ?")) { + try (PreparedStatement stmt = connection.prepareStatement("SELECT * FROM tokens WHERE uuid = ?")) { stmt.setString(1, uuid.toString()); ResultSet rs = stmt.executeQuery(); if (rs.next()) { Token token = new Token(rs.getString("id"), - new AccessToken(rs.getString("access_token"), rs.getString("refresh_token"))); + new AccessToken(rs.getString("access_token"), rs.getString("refresh_token"))); return Optional.of(token); } return Optional.empty(); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c4088b3..b0d9b96 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -23,9 +23,6 @@ upgrades: - rank: ubersub threshold: 2 -# remove roles from users that got subscription roles manually -sync-roles: true - # The client ID given by Twitch. client-id: required From ece65fbeeef80e563cf189d795c06196d5a4836c Mon Sep 17 00:00:00 2001 From: cfi Date: Wed, 12 Jun 2019 13:30:17 +0200 Subject: [PATCH 3/8] continue refactor --- .../java/me/okx/twitchsync/TwitchServer.java | 30 +---- .../me/okx/twitchsync/TwitchSyncCommand.java | 38 ++++-- .../java/me/okx/twitchsync/Validator.java | 110 ++++++++++++------ .../okx/twitchsync/data/OptionSupplier.java | 7 ++ .../java/me/okx/twitchsync/data/Upgrade.java | 28 +++++ .../okx/twitchsync/data/sync/SyncMessage.java | 2 + .../twitchsync/data/sync/SyncResponse.java | 33 +++++- .../data/sync/SyncResponseFailure.java | 13 --- .../data/sync/SyncResponseSuccess.java | 27 ----- .../me/okx/twitchsync/util/SqlHelper.java | 1 - 10 files changed, 170 insertions(+), 119 deletions(-) create mode 100644 src/main/java/me/okx/twitchsync/data/OptionSupplier.java create mode 100644 src/main/java/me/okx/twitchsync/data/Upgrade.java delete mode 100644 src/main/java/me/okx/twitchsync/data/sync/SyncResponseFailure.java delete mode 100644 src/main/java/me/okx/twitchsync/data/sync/SyncResponseSuccess.java diff --git a/src/main/java/me/okx/twitchsync/TwitchServer.java b/src/main/java/me/okx/twitchsync/TwitchServer.java index fedd1b7..447a0c6 100644 --- a/src/main/java/me/okx/twitchsync/TwitchServer.java +++ b/src/main/java/me/okx/twitchsync/TwitchServer.java @@ -4,8 +4,6 @@ import me.okx.twitchsync.data.Token; import me.okx.twitchsync.data.sync.SyncMessage; import me.okx.twitchsync.data.sync.SyncResponse; -import me.okx.twitchsync.data.sync.SyncResponseFailure; -import me.okx.twitchsync.data.sync.SyncResponseSuccess; import java.io.IOException; import java.io.OutputStream; @@ -31,7 +29,7 @@ public void start() throws IOException { try { plugin.debug("Handling request " + ex + " on " + Thread.currentThread().getName()); - SyncResponse response = new SyncResponseFailure(SyncMessage.INVALID_URL); + SyncResponse response = SyncResponse.of(SyncMessage.INVALID_URL); String query = ex.getRequestURI().getQuery(); if (query != null) { @@ -47,31 +45,7 @@ public void start() throws IOException { } } - SyncMessage message; - if (response instanceof SyncResponseSuccess) { - SyncResponseSuccess success = (SyncResponseSuccess) response; - if (success.isFollowing() && success.isSubscribed()) { - message = SyncMessage.BOTH_SUCCESS; - } else if (success.isFollowing()) { - message = SyncMessage.FOLLOW_SUCCESS; - } else if (success.isSubscribed()) { - message = SyncMessage.SUBSCRIPTION_SUCCESS; - } else { - SyncMessage subscribe = success.getSubscribeMessage(); - SyncMessage follow = success.getFollowMessage(); - // make sure the already-done message shows up - if(subscribe == SyncMessage.ALREADY_DONE) { - message = subscribe; - } else { - message = follow; - } - } - } else if (response instanceof SyncResponseFailure) { - SyncResponseFailure failure = (SyncResponseFailure) response; - message = failure.getMessage(); - } else { - throw new IllegalArgumentException("Sync response must either be success or failure."); - } + SyncMessage message = response.getMessage(); byte[] bytes = message.getValue(plugin).getBytes("UTF-8"); diff --git a/src/main/java/me/okx/twitchsync/TwitchSyncCommand.java b/src/main/java/me/okx/twitchsync/TwitchSyncCommand.java index 365ad1e..d2ef885 100644 --- a/src/main/java/me/okx/twitchsync/TwitchSyncCommand.java +++ b/src/main/java/me/okx/twitchsync/TwitchSyncCommand.java @@ -1,5 +1,7 @@ package me.okx.twitchsync; +import me.okx.twitchsync.data.sync.SyncMessage; +import me.okx.twitchsync.data.sync.SyncResponse; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; @@ -9,6 +11,9 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + public class TwitchSyncCommand implements CommandExecutor { private TwitchSync plugin; @@ -24,18 +29,27 @@ public boolean onCommand(CommandSender cs, Command command, String s, String[] a } Player player = (Player) cs; - - String url = plugin.getValidator().createAuthenticationUrl(player.getUniqueId()); - if(url == null) { - player.sendMessage(ChatColor.RED + "An error occurred. Please try again."); - return true; - } - - player.spigot().sendMessage(new ComponentBuilder("Click this text to sync to Twitch") - .color(net.md_5.bungee.api.ChatColor.GREEN) - .event(new ClickEvent(ClickEvent.Action.OPEN_URL, url)) - .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to sync to Twitch").create())) - .create()); + plugin.getValidator().sync(player.getUniqueId()).whenCompleteAsync((response, throwable) -> { + if (throwable != null) { + player.sendMessage(ChatColor.translateAlternateColorCodes('&', "&6An error occurred. Please try again.")); + plugin.getLogger().warning(throwable.toString()); + throwable.printStackTrace(); + } + if (response.getMessage() != SyncMessage.NO_TOKEN) { + player.sendMessage(ChatColor.GREEN + "Re-synced."); + } else { + String url = plugin.getValidator().createAuthenticationUrl(player.getUniqueId()); + if(url == null) { + player.sendMessage(ChatColor.RED + "An error occurred. Please try again."); + return; + } + player.spigot().sendMessage(new ComponentBuilder("Click this text to sync to Twitch") + .color(net.md_5.bungee.api.ChatColor.GREEN) + .event(new ClickEvent(ClickEvent.Action.OPEN_URL, url)) + .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to sync to Twitch").create())) + .create()); + } + }); return true; } diff --git a/src/main/java/me/okx/twitchsync/Validator.java b/src/main/java/me/okx/twitchsync/Validator.java index 79b3b75..5931603 100644 --- a/src/main/java/me/okx/twitchsync/Validator.java +++ b/src/main/java/me/okx/twitchsync/Validator.java @@ -21,10 +21,6 @@ import me.okx.twitchsync.data.json.Users; import me.okx.twitchsync.data.sync.SyncMessage; import me.okx.twitchsync.data.sync.SyncResponse; -import me.okx.twitchsync.data.sync.SyncResponseFailure; -import me.okx.twitchsync.data.sync.SyncResponseSuccess; -import me.okx.twitchsync.events.PlayerFollowEvent; -import me.okx.twitchsync.events.PlayerSubscriptionEvent; import me.okx.twitchsync.util.SqlHelper; import me.okx.twitchsync.util.WebUtil; import net.milkbowl.vault.permission.Permission; @@ -45,7 +41,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Comparator.reverseOrder; + public class Validator { + public static final String SUBSCRIPTIONS = "subscriptions"; + public static final String FOLLOWS = "follows/channels"; private Gson gson = new Gson(); private Map channels; private TwitchSync plugin; @@ -57,18 +57,25 @@ public Validator(TwitchSync plugin) { .build(); this.plugin = plugin; CompletableFuture.runAsync(() -> { - List channelList = new ArrayList<>(); - ConfigurationSection channelConfig = plugin.getConfig().getConfigurationSection("channels"); - if (channelConfig != null) { - Set channelNames = channelConfig.getKeys(false); - for (String name : channelNames) { - channelList.add(channelConfig.getObject(name, Channel.class)); - } - } + List channelList = getList("channels", Channel.class); this.channels = getChannelMap(channelList); }); } + private List getList(String path, Class clazz) { + List _list = plugin.getConfig().getList(path); + List list = new ArrayList<>(); + + if (_list == null) return list; + + for (Object t : _list) { + if (clazz.isInstance(t)) { + list.add(clazz.cast(t)); + } + } + return list; + } + public Channel getChannel(int channelId) { return channels.get(channelId); } @@ -263,45 +270,74 @@ public CompletableFuture sync(Map users) { return CompletableFuture.runAsync(() -> users.forEach(this::sync)); } - public CompletableFuture sync(UUID uuid) { - return CompletableFuture.runAsync(() -> plugin.getSqlHelper().getToken(uuid).ifPresent(token -> sync(uuid, token).join())); + public CompletableFuture sync(UUID uuid) { + return CompletableFuture.supplyAsync(() -> + plugin.getSqlHelper().getToken(uuid).map(token -> + sync(uuid, token).join()).orElse(SyncResponse.of(SyncMessage.NO_TOKEN))); } public CompletableFuture sync(UUID uuid, Token token) { return CompletableFuture.supplyAsync(() -> { if (token == null) { - return null; + return SyncResponse.of(SyncMessage.NO_TOKEN); } token.setAccessToken(refreshAndSaveToken(uuid, token)); - Stream states = getStates(token, channels, "subscriptions"); - SqlHelper helper = plugin.getSqlHelper(); + Stream subscribeStates = getStates(token, channels, SUBSCRIPTIONS); + List subscriptions = syncStates(uuid, subscribeStates, Channel::getSubscribeOptions); + + Stream followStates = getStates(token, channels, FOLLOWS); + List follows = syncStates(uuid, followStates, Channel::getFollowOptions); + + Integer subscriptionCount = subscriptions.size(); + // get highest upgrade + // get upgrades + List upgrades = getList("upgrades", Upgrade.class); + upgrades.sort(Comparator.comparingInt(Upgrade::getThreshold)); + OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); Permission perms = plugin.getPerms(); - states.forEach(state -> { - Boolean subscribed = null; - if (state.getState() == CheckState.YES) subscribed = true; - else if (state.getState() == CheckState.NO) subscribed = false; - if (subscribed != null) { - helper.setSubscribed(uuid, state.getChannel().getName(), subscribed); - OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); - String rank = state.getChannel().getSubscribeOptions().getRank(); - if (subscribed) { - if (!perms.playerInGroup(null, player, rank)) { - perms.playerAddGroup(null, player, rank); - } - } else { - if (perms.playerInGroup(null, player, rank)) { - perms.playerRemoveGroup(null, player, rank); - } + + for (Upgrade u : upgrades) { + if (u.getThreshold() >= subscriptions.size()) { + if (!perms.playerInGroup(null, player, u.getRank())) { + perms.playerAddGroup(null, player, u.getRank()); } + } else if (perms.playerInGroup(null, player, u.getRank())) { + perms.playerRemoveGroup(null, player, u.getRank()); } - }); - // todo: compare with database - // todo: check each channel and sync roles + } + // todo: check total subscription count for upgrades - return null; + return new SyncResponse(SyncMessage.SUCCESS, subscriptions, follows); + }); + } + + private List syncStates(UUID uuid, Stream states, OptionSupplier optionSupplier) { + List subscriptions = new ArrayList<>(); + SqlHelper helper = plugin.getSqlHelper(); + Permission perms = plugin.getPerms(); + states.forEach(state -> { + Boolean active = null; + if (state.getState() == CheckState.YES) active = true; + else if (state.getState() == CheckState.NO) active = false; + if (active != null) { + helper.setSubscribed(uuid, state.getChannel().getName(), active); + OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); + String rank = optionSupplier.supply(state.getChannel()).getRank(); + if (active) { + if (!perms.playerInGroup(null, player, rank)) { + perms.playerAddGroup(null, player, rank); + } + subscriptions.add(state.getChannel()); + } else { + if (perms.playerInGroup(null, player, rank)) { + perms.playerRemoveGroup(null, player, rank); + } + } + } }); + return subscriptions; } private AccessToken refreshAndSaveToken(UUID uuid, Token token) { diff --git a/src/main/java/me/okx/twitchsync/data/OptionSupplier.java b/src/main/java/me/okx/twitchsync/data/OptionSupplier.java new file mode 100644 index 0000000..ca36133 --- /dev/null +++ b/src/main/java/me/okx/twitchsync/data/OptionSupplier.java @@ -0,0 +1,7 @@ +package me.okx.twitchsync.data; + +public interface OptionSupplier { + + public Options supply(Channel channel); + +} diff --git a/src/main/java/me/okx/twitchsync/data/Upgrade.java b/src/main/java/me/okx/twitchsync/data/Upgrade.java new file mode 100644 index 0000000..374ba66 --- /dev/null +++ b/src/main/java/me/okx/twitchsync/data/Upgrade.java @@ -0,0 +1,28 @@ +package me.okx.twitchsync.data; + +public class Upgrade { + + private Integer threshold; + private String rank; + + public Upgrade(Integer threshold, String rank) { + this.threshold = threshold; + this.rank = rank; + } + + public Integer getThreshold() { + return threshold; + } + + public void setThreshold(Integer threshold) { + this.threshold = threshold; + } + + public String getRank() { + return rank; + } + + public void setRank(String rank) { + this.rank = rank; + } +} diff --git a/src/main/java/me/okx/twitchsync/data/sync/SyncMessage.java b/src/main/java/me/okx/twitchsync/data/sync/SyncMessage.java index 46fd59c..894d7a5 100644 --- a/src/main/java/me/okx/twitchsync/data/sync/SyncMessage.java +++ b/src/main/java/me/okx/twitchsync/data/sync/SyncMessage.java @@ -12,6 +12,8 @@ public enum SyncMessage { UNKNOWN_ERROR("unknown-error"), BOTH_SUCCESS("success-both"), NOT_BOTH("not-both"), + SUCCESS("success"), + NO_TOKEN("no-token"), ALREADY_DONE("already-done"); private String config; diff --git a/src/main/java/me/okx/twitchsync/data/sync/SyncResponse.java b/src/main/java/me/okx/twitchsync/data/sync/SyncResponse.java index 3109684..66011b0 100644 --- a/src/main/java/me/okx/twitchsync/data/sync/SyncResponse.java +++ b/src/main/java/me/okx/twitchsync/data/sync/SyncResponse.java @@ -1,5 +1,36 @@ package me.okx.twitchsync.data.sync; -public abstract class SyncResponse { +import me.okx.twitchsync.data.Channel; + +import java.util.Collections; +import java.util.List; + +public class SyncResponse { + + private SyncMessage message; + private List subscriptions; + private List follows; + + public SyncResponse(SyncMessage message, List subscriptions, List follows) { + this.message = message; + this.subscriptions = subscriptions; + this.follows = follows; + } + + public static SyncResponse of(SyncMessage message) { + return new SyncResponse(message, Collections.emptyList(), Collections.emptyList()); + } + + public List getSubscriptions() { + return subscriptions; + } + + public List getFollows() { + return follows; + } + + public SyncMessage getMessage() { + return message; + } } diff --git a/src/main/java/me/okx/twitchsync/data/sync/SyncResponseFailure.java b/src/main/java/me/okx/twitchsync/data/sync/SyncResponseFailure.java deleted file mode 100644 index 3a0fbf5..0000000 --- a/src/main/java/me/okx/twitchsync/data/sync/SyncResponseFailure.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.okx.twitchsync.data.sync; - -public class SyncResponseFailure extends SyncResponse { - private SyncMessage message; - - public SyncResponseFailure(SyncMessage message) { - this.message = message; - } - - public SyncMessage getMessage() { - return message; - } -} diff --git a/src/main/java/me/okx/twitchsync/data/sync/SyncResponseSuccess.java b/src/main/java/me/okx/twitchsync/data/sync/SyncResponseSuccess.java deleted file mode 100644 index 47e5eca..0000000 --- a/src/main/java/me/okx/twitchsync/data/sync/SyncResponseSuccess.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.okx.twitchsync.data.sync; - -public class SyncResponseSuccess extends SyncResponse { - private SyncMessage follow; - private SyncMessage subscribe; - - public SyncResponseSuccess(SyncMessage follow, SyncMessage subscribe) { - this.follow = follow; - this.subscribe = subscribe; - } - - public boolean isFollowing() { - return follow == SyncMessage.FOLLOW_SUCCESS; - } - - public SyncMessage getFollowMessage() { - return follow; - } - - public boolean isSubscribed() { - return subscribe == SyncMessage.SUBSCRIPTION_SUCCESS; - } - - public SyncMessage getSubscribeMessage() { - return subscribe; - } -} diff --git a/src/main/java/me/okx/twitchsync/util/SqlHelper.java b/src/main/java/me/okx/twitchsync/util/SqlHelper.java index 410372d..f30c86e 100644 --- a/src/main/java/me/okx/twitchsync/util/SqlHelper.java +++ b/src/main/java/me/okx/twitchsync/util/SqlHelper.java @@ -25,7 +25,6 @@ public SqlHelper(TwitchSync plugin) { Statement stmt = connection.createStatement(); stmt.execute("CREATE TABLE IF NOT EXISTS subscribed (uuid VARCHAR(36), channel VARCHAR(25), PRIMARY KEY (uuid, channel))"); stmt.execute("CREATE TABLE IF NOT EXISTS following (uuid VARCHAR(36), channel VARCHAR(25), PRIMARY KEY (uuid, channel))"); - stmt.execute("CREATE TABLE IF NOT EXISTS credentials (userId NVARCHAR PRIMARY KEY, identProvider NVARCHAR)"); stmt.execute("CREATE TABLE IF NOT EXISTS tokens " + "(uuid VARCHAR(36) PRIMARY KEY, " + "id VARCHAR(12), " + From 345b8cd83addd5ddb4361c9ca04909aeeb5ba0df Mon Sep 17 00:00:00 2001 From: cfi Date: Wed, 12 Jun 2019 13:48:45 +0200 Subject: [PATCH 4/8] continue refactor --- .../java/me/okx/twitchsync/Validator.java | 28 +++++++++++++++---- .../twitchsync/events/PlayerFollowEvent.java | 11 ++++---- .../okx/twitchsync/events/PlayerListener.java | 22 +++++++-------- .../events/PlayerSubscriptionEvent.java | 12 ++++---- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/main/java/me/okx/twitchsync/Validator.java b/src/main/java/me/okx/twitchsync/Validator.java index 5931603..e98470f 100644 --- a/src/main/java/me/okx/twitchsync/Validator.java +++ b/src/main/java/me/okx/twitchsync/Validator.java @@ -21,6 +21,7 @@ import me.okx.twitchsync.data.json.Users; import me.okx.twitchsync.data.sync.SyncMessage; import me.okx.twitchsync.data.sync.SyncResponse; +import me.okx.twitchsync.events.PlayerSubscriptionEvent; import me.okx.twitchsync.util.SqlHelper; import me.okx.twitchsync.util.WebUtil; import net.milkbowl.vault.permission.Permission; @@ -324,15 +325,32 @@ private List syncStates(UUID uuid, Stream states, OptionSu if (active != null) { helper.setSubscribed(uuid, state.getChannel().getName(), active); OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); - String rank = optionSupplier.supply(state.getChannel()).getRank(); + Options options = optionSupplier.supply(state.getChannel()); if (active) { - if (!perms.playerInGroup(null, player, rank)) { - perms.playerAddGroup(null, player, rank); + if (!perms.playerInGroup(null, player, options.getRank())) { + if (player.isOnline()) { + Player onlinePlayer = (Player) player; + Bukkit.getScheduler().runTask(plugin, () -> + Bukkit.getPluginManager().callEvent( + new PlayerSubscriptionEvent(onlinePlayer, state.getChannel()))); + } else { + if (options.getEnabled()) { + if (!options.getRank().equalsIgnoreCase("none") && plugin.getPerms() != null) { + plugin.getPerms().playerAddGroup(null, player, options.getRank()); + } + for (String command : options.getCommands()) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command + .replace("%name%", player.getName()) + .replace("%channel%", state.getChannel().getName()) + .replace("%channelid%", state.getId() + "")); + } + } + } } subscriptions.add(state.getChannel()); } else { - if (perms.playerInGroup(null, player, rank)) { - perms.playerRemoveGroup(null, player, rank); + if (perms.playerInGroup(null, player, options.getRank())) { + perms.playerRemoveGroup(null, player, options.getRank()); } } } diff --git a/src/main/java/me/okx/twitchsync/events/PlayerFollowEvent.java b/src/main/java/me/okx/twitchsync/events/PlayerFollowEvent.java index 0348b73..23ffa0e 100644 --- a/src/main/java/me/okx/twitchsync/events/PlayerFollowEvent.java +++ b/src/main/java/me/okx/twitchsync/events/PlayerFollowEvent.java @@ -1,5 +1,6 @@ package me.okx.twitchsync.events; +import me.okx.twitchsync.data.Channel; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; @@ -7,23 +8,23 @@ public class PlayerFollowEvent extends PlayerEvent { private static final HandlerList handlers = new HandlerList(); - private int channelId; + private Channel channel; /** * Fired when a player subscription event is triggered * * @param who The player who subscribed */ - public PlayerFollowEvent(Player who, int channelId) { + public PlayerFollowEvent(Player who, Channel channel) { super(who); - this.channelId = channelId; + this.channel = channel; } /** * @return The channel ID the user has subscribed to. */ - public int getChannelId() { - return channelId; + public Channel getChannel() { + return channel; } @Override diff --git a/src/main/java/me/okx/twitchsync/events/PlayerListener.java b/src/main/java/me/okx/twitchsync/events/PlayerListener.java index e78014b..c4b885f 100644 --- a/src/main/java/me/okx/twitchsync/events/PlayerListener.java +++ b/src/main/java/me/okx/twitchsync/events/PlayerListener.java @@ -1,6 +1,9 @@ package me.okx.twitchsync.events; import me.okx.twitchsync.TwitchSync; +import me.okx.twitchsync.data.Channel; +import me.okx.twitchsync.data.OptionSupplier; +import me.okx.twitchsync.data.Options; import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; @@ -16,32 +19,29 @@ public PlayerListener(TwitchSync plugin) { @EventHandler public void on(PlayerSubscriptionEvent e) { - handle("subscribe", e.getPlayer(), e.getChannelId()); + handle(Channel::getSubscribeOptions, e.getPlayer(), e.getChannel()); } @EventHandler public void on(PlayerFollowEvent e) { - handle("follow", e.getPlayer(), e.getChannelId()); + handle(Channel::getFollowOptions, e.getPlayer(), e.getChannel()); } - private void handle(String path, Player player, int channelId) { - ConfigurationSection config = plugin.getConfig().getConfigurationSection(path); - if(!config.getBoolean("enabled")) { + private void handle(OptionSupplier optionSupplier, Player player, Channel channel) { + Options options = optionSupplier.supply(channel); + if(!options.getEnabled()) { return; } - String channel = plugin.getValidator().getChannelName(channelId); - - String group = config.getString("rank"); + String group = options.getRank(); if (!group.equalsIgnoreCase("none") && plugin.getPerms() != null) { plugin.getPerms().playerAddGroup(null, player, group); } - for (String command : config.getStringList("commands")) { + for (String command : options.getCommands()) { Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command .replace("%name%", player.getName()) - .replace("%channel%", channel) - .replace("%channelid%", channelId + "")); + .replace("%channel%", channel.getName())); } } } diff --git a/src/main/java/me/okx/twitchsync/events/PlayerSubscriptionEvent.java b/src/main/java/me/okx/twitchsync/events/PlayerSubscriptionEvent.java index 8f602c5..0bb46a0 100644 --- a/src/main/java/me/okx/twitchsync/events/PlayerSubscriptionEvent.java +++ b/src/main/java/me/okx/twitchsync/events/PlayerSubscriptionEvent.java @@ -1,5 +1,7 @@ package me.okx.twitchsync.events; +import me.okx.twitchsync.data.Channel; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; @@ -7,23 +9,23 @@ public class PlayerSubscriptionEvent extends PlayerEvent { private static final HandlerList handlers = new HandlerList(); - private int channelId; + private Channel channel; /** * Fired when a player subscription event is triggered * * @param who The player who subscribed */ - public PlayerSubscriptionEvent(Player who, int channelId) { + public PlayerSubscriptionEvent(Player who, Channel channel) { super(who); - this.channelId = channelId; + this.channel = channel; } /** * @return The channel ID the user has subscribed to. */ - public int getChannelId() { - return channelId; + public Channel getChannel() { + return channel; } @Override From 0575c709e1aae17c532c2cefce8ef637079a8b1b Mon Sep 17 00:00:00 2001 From: cfi Date: Wed, 12 Jun 2019 21:18:18 +0200 Subject: [PATCH 5/8] continue refactor --- .gitignore | 4 +- .../java/me/okx/twitchsync/TwitchSync.java | 18 +- .../java/me/okx/twitchsync/Validator.java | 188 ++++++++---------- .../java/me/okx/twitchsync/data/Channel.java | 47 ++++- .../java/me/okx/twitchsync/data/Options.java | 35 +++- .../java/me/okx/twitchsync/data/Token.java | 4 + .../java/me/okx/twitchsync/data/Upgrade.java | 21 +- .../okx/twitchsync/events/PlayerListener.java | 9 +- src/main/resources/config.yml | 33 ++- 9 files changed, 224 insertions(+), 135 deletions(-) diff --git a/.gitignore b/.gitignore index cd67a55..fa49c98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ \.gradle/ \.idea/ build/ -*.iml \ No newline at end of file +*.iml + +*.db \ No newline at end of file diff --git a/src/main/java/me/okx/twitchsync/TwitchSync.java b/src/main/java/me/okx/twitchsync/TwitchSync.java index 16e09d2..527eece 100644 --- a/src/main/java/me/okx/twitchsync/TwitchSync.java +++ b/src/main/java/me/okx/twitchsync/TwitchSync.java @@ -1,8 +1,12 @@ package me.okx.twitchsync; +import me.okx.twitchsync.data.Channel; +import me.okx.twitchsync.data.Options; +import me.okx.twitchsync.data.Upgrade; import me.okx.twitchsync.events.PlayerListener; import me.okx.twitchsync.util.SqlHelper; import net.milkbowl.vault.permission.Permission; +import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; @@ -19,8 +23,16 @@ public Permission getPerms() { return this.perms; } + @Override + public void onLoad() { + ConfigurationSerialization.registerClass(Options.class); + ConfigurationSerialization.registerClass(Channel.class); + ConfigurationSerialization.registerClass(Upgrade.class); + } + @Override public void onEnable() { + debug("Logging works.", "Testing"); getConfig().options().copyDefaults(true); saveDefaultConfig(); initValidator(); @@ -80,21 +92,21 @@ public Validator getValidator() { } public T debug(T message) { - if (getConfig().getBoolean("debug-mode")) { + if (getConfig().getBoolean("debug-mode") || true) { getLogger().log(Level.INFO, String.valueOf(message)); } return message; } public T debug(T message, String label) { - if (getConfig().getBoolean("debug-mode")) { + if (getConfig().getBoolean("debug-mode") || true) { getLogger().log(Level.INFO, label + ": " + message); } return message; } public void debug(Throwable throwable) { - if (getConfig().getBoolean("debug-mode")) { + if (getConfig().getBoolean("debug-mode") || true) { throwable.printStackTrace(); } } diff --git a/src/main/java/me/okx/twitchsync/Validator.java b/src/main/java/me/okx/twitchsync/Validator.java index e98470f..3a45162 100644 --- a/src/main/java/me/okx/twitchsync/Validator.java +++ b/src/main/java/me/okx/twitchsync/Validator.java @@ -26,8 +26,8 @@ import me.okx.twitchsync.util.WebUtil; import net.milkbowl.vault.permission.Permission; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import java.io.IOException; @@ -42,8 +42,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.Comparator.reverseOrder; - public class Validator { public static final String SUBSCRIPTIONS = "subscriptions"; public static final String FOLLOWS = "follows/channels"; @@ -58,16 +56,36 @@ public Validator(TwitchSync plugin) { .build(); this.plugin = plugin; CompletableFuture.runAsync(() -> { - List channelList = getList("channels", Channel.class); + plugin.debug("Getting Channels.", "Testing"); + List channelList = (List)plugin.getConfig().getList("channels"); + plugin.debug(channelList, "Channels"); + if (channelList == null) channelList = new ArrayList<>(); this.channels = getChannelMap(channelList); + plugin.debug("Checking channels: " + channelList.stream().map(Channel::getName).collect(Collectors.joining(",")), "Channels"); + plugin.debug("Got channels: " + this.channels.values().stream().map(Channel::getName).collect(Collectors.joining(",")), "Channels"); + if (channelList.size() == 0) { + plugin.debug("Got an empty channel list, writing an example to file.", "Channels"); + Channel example = new Channel(); + example.setName("example"); + Options exampleOptions = new Options(); + exampleOptions.setRank("none"); + exampleOptions.setEnabled(false); + exampleOptions.setCommands(Collections.emptyList()); + exampleOptions.setRevokeCommands(Collections.emptyList()); + example.setSubscribe(exampleOptions); + example.setFollow(exampleOptions); + channelList.add(example); + plugin.getConfig().set("channels", channelList); + plugin.saveConfig(); + } }); } private List getList(String path, Class clazz) { List _list = plugin.getConfig().getList(path); List list = new ArrayList<>(); - if (_list == null) return list; + plugin.debug(_list, "Channels"); for (Object t : _list) { if (clazz.isInstance(t)) { @@ -82,25 +100,35 @@ public Channel getChannel(int channelId) { } private Map getChannelMap(List channels) { - Map headers = new HashMap<>(); + try { + Map headers = new HashMap<>(); - headers.put("Client-ID", plugin.getConfig().getString("client-id")); - headers.put("Accept", "application/vnd.twitchtv.v5+json"); + headers.put("Client-ID", plugin.getConfig().getString("client-id")); + headers.put("Accept", "application/vnd.twitchtv.v5+json"); - Reader reader = WebUtil.getURL("https://api.twitch.tv/kraken/users?login=" - + channels.stream().map(Channel::getName).collect(Collectors.joining(",")), headers); - Users users = plugin.debug(gson.fromJson(reader, Users.class), "Users"); + if (channels.size() == 0) { + plugin.debug("No channels found", "Error"); + return new HashMap<>(); + } - if (users.getTotal() == 0) { - plugin.getLogger().log(Level.SEVERE, "No channels found"); - return null; - } + Reader reader = WebUtil.getURL("https://api.twitch.tv/kraken/users?login=" + + channels.stream().map(Channel::getName).collect(Collectors.joining(",")), headers); + Users users = plugin.debug(gson.fromJson(reader, Users.class), "Users"); + + if (users.getTotal() == 0) { + plugin.debug("No channels found", "Error"); + return new HashMap<>(); + } - Map channelMap = new HashMap<>(); - for (User user : users.getUsers()) { - channelMap.put(user.getId(), channels.stream().filter(c -> c.getName().equals(user.getName())).findFirst().get()); + Map channelMap = new HashMap<>(); + for (User user : users.getUsers()) { + channelMap.put(user.getId(), channels.stream().filter(c -> c.getName().equals(user.getName())).findFirst().get()); + } + return channelMap; + } catch (Exception ex) { + plugin.debug(ex); + return new HashMap<>(); } - return channelMap; } public String createAuthenticationUrl(UUID user) { @@ -115,77 +143,6 @@ public String createAuthenticationUrl(UUID user) { "&state=" + uuid; } - /* - public SyncResponse sync(UUID withState, String code) { - plugin.debug("States: " + userStates.asMap()); - - UUID uuid = userStates.getIfPresent(withState); - if (uuid == null) { - return new SyncResponseFailure(SyncMessage.STATE_NOT_FOUND); - } - userStates.invalidate(withState); - - // safe to run blocking sql as this is run on web server thread - Optional following = plugin.getSqlHelper().isFollowing(uuid); - Optional subscribed = plugin.getSqlHelper().isSubscribed(uuid); - - if (!subscribed.isPresent() || !following.isPresent()) { - return new SyncResponseFailure(SyncMessage.UNKNOWN_ERROR); - } - - Player user = Bukkit.getPlayer(uuid); - - if (user == null) { - return new SyncResponseFailure(SyncMessage.PLAYER_NOT_FOUND); - } - - AccessToken token; - try { - token = plugin.debug(getAccessToken(code)); - } catch (Exception ex) { - plugin.debug(ex); - return new SyncResponseFailure(SyncMessage.UNKNOWN_ERROR); - } - - - try { - String userId = plugin.debug(getUserId(token), "User ID"); - plugin.getSqlHelper().setToken(uuid, userId, token.getAccessToken(), token.getRefreshToken()); - - MessageWithId subscriptionMessage = getSubscriptionMessage(userId, token); - if (subscriptionMessage.getMessage() == SyncMessage.SUBSCRIPTION_SUCCESS) { - if (plugin.debug(subscribed.get(), "Subscribed")) { - subscriptionMessage.setMessage(SyncMessage.ALREADY_DONE); - } else { - Bukkit.getScheduler().runTask(plugin, () -> - Bukkit.getPluginManager().callEvent( - new PlayerSubscriptionEvent(user, subscriptionMessage.getChannelId().get()))); - plugin.getSqlHelper().setSubscribed(uuid, true); - } - } - - MessageWithId followingMessage = getFollowingMessage(userId, token); - if (followingMessage.getMessage() == SyncMessage.FOLLOW_SUCCESS) { - if (plugin.debug(following.get(), "Followed")) { - followingMessage.setMessage(SyncMessage.ALREADY_DONE); - } else { - Bukkit.getScheduler().runTask(plugin, () -> - Bukkit.getPluginManager().callEvent( - new PlayerFollowEvent(user, followingMessage.getChannelId().get()))); - plugin.getSqlHelper().setFollowing(uuid, true); - } - } - - return new SyncResponseSuccess( - followingMessage.getMessage(), subscriptionMessage.getMessage()); - } catch (Exception ex) { - // TODO: More information based on error type - plugin.debug(ex); - return null; - } - } - */ - private String getUserId(AccessToken token) throws IOException, ParseException, BadJOSEException, JOSEException { Issuer iss = new Issuer("https://id.twitch.tv/oauth2"); @@ -227,7 +184,7 @@ private Stream getStates(Token token, Map channel } private StateWithId getIndividualState(Channel channel, Map headers, String url, int channelId) { - InputStreamReader reader = WebUtil.getURL(url + channelId, headers); + InputStreamReader reader = WebUtil.getURL(plugin.debug(url + channelId, "Sync"), headers); JsonElement json = gson.fromJson(reader, JsonElement.class); ChannelObject object = gson.fromJson(json, ChannelObject.class); @@ -274,7 +231,8 @@ public CompletableFuture sync(Map users) { public CompletableFuture sync(UUID uuid) { return CompletableFuture.supplyAsync(() -> plugin.getSqlHelper().getToken(uuid).map(token -> - sync(uuid, token).join()).orElse(SyncResponse.of(SyncMessage.NO_TOKEN))); + sync(plugin.debug(uuid), plugin.debug(token)).join()) + .orElse(SyncResponse.of(SyncMessage.NO_TOKEN))); } public CompletableFuture sync(UUID uuid, Token token) { @@ -283,13 +241,35 @@ public CompletableFuture sync(UUID uuid, Token token) { return SyncResponse.of(SyncMessage.NO_TOKEN); } - token.setAccessToken(refreshAndSaveToken(uuid, token)); + if (channels == null) { + Player player = Bukkit.getPlayer(uuid); + if (player != null) { + player.sendMessage(ChatColor.DARK_RED + "No channels available, contact your administrator."); + } + plugin.getLogger().info("No channels found to check for subscriptions."); + return SyncResponse.of(SyncMessage.SUCCESS); + } + + Token _token = refreshAndSaveToken(uuid, token); + + if (_token == null) { + _token = plugin.getSqlHelper().getToken(uuid).get(); + } + + if (_token.getId() == null) { + try { + _token.setId(getUserId(_token.getAccessToken())); + } catch (Exception ex) { + plugin.debug(ex); + } + } + plugin.debug("ID: " + _token.getId(), "Sync"); - Stream subscribeStates = getStates(token, channels, SUBSCRIPTIONS); - List subscriptions = syncStates(uuid, subscribeStates, Channel::getSubscribeOptions); + Stream subscribeStates = getStates(_token, channels, SUBSCRIPTIONS); + List subscriptions = syncStates(uuid, subscribeStates, Channel::getSubscribe); - Stream followStates = getStates(token, channels, FOLLOWS); - List follows = syncStates(uuid, followStates, Channel::getFollowOptions); + Stream followStates = getStates(_token, channels, FOLLOWS); + List follows = syncStates(uuid, followStates, Channel::getFollow); Integer subscriptionCount = subscriptions.size(); // get highest upgrade @@ -322,6 +302,7 @@ private List syncStates(UUID uuid, Stream states, OptionSu Boolean active = null; if (state.getState() == CheckState.YES) active = true; else if (state.getState() == CheckState.NO) active = false; + plugin.debug(uuid.toString() + " - " + state.getChannel().getName() + ": " + state.getState().toString(), "Sync"); if (active != null) { helper.setSubscribed(uuid, state.getChannel().getName(), active); OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); @@ -329,6 +310,7 @@ private List syncStates(UUID uuid, Stream states, OptionSu if (active) { if (!perms.playerInGroup(null, player, options.getRank())) { if (player.isOnline()) { + plugin.debug("Player is online, running event as normal", "Sync"); Player onlinePlayer = (Player) player; Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getPluginManager().callEvent( @@ -358,10 +340,14 @@ private List syncStates(UUID uuid, Stream states, OptionSu return subscriptions; } - private AccessToken refreshAndSaveToken(UUID uuid, Token token) { - AccessToken refresh = refreshToken(token.getAccessToken().getRefreshToken()); - plugin.getSqlHelper().setToken(uuid, token.getId(), refresh); - return refresh; + private Token refreshAndSaveToken(UUID uuid, Token token) { + AccessToken newToken = refreshToken(token.getAccessToken().getRefreshToken()); + try { + return plugin.getSqlHelper().setToken(uuid, token.getId(), newToken).get(); + } catch (Exception ex) { + plugin.debug(ex); + return null; + } } } diff --git a/src/main/java/me/okx/twitchsync/data/Channel.java b/src/main/java/me/okx/twitchsync/data/Channel.java index fd8cef3..aae4994 100644 --- a/src/main/java/me/okx/twitchsync/data/Channel.java +++ b/src/main/java/me/okx/twitchsync/data/Channel.java @@ -1,10 +1,19 @@ package me.okx.twitchsync.data; -public class Channel { +import com.google.common.eventbus.Subscribe; +import org.bukkit.ChatColor; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import java.util.HashMap; +import java.util.Map; + +@SerializableAs("channel") +public class Channel implements ConfigurationSerializable { private String name; - private Options subscribeOptions; - private Options followOptions; + private Options subscribe; + private Options follow; public String getName() { return name; @@ -14,20 +23,36 @@ public void setName(String name) { this.name = name; } - public Options getSubscribeOptions() { - return subscribeOptions; + public Options getSubscribe() { + return subscribe; } - public void setSubscribeOptions(Options subscribeOptions) { - this.subscribeOptions = subscribeOptions; + public void setSubscribe(Options subscribe) { + this.subscribe = subscribe; } - public Options getFollowOptions() { - return followOptions; + public Options getFollow() { + return follow; } - public void setFollowOptions(Options followOptions) { - this.followOptions = followOptions; + public void setFollow(Options follow) { + this.follow = follow; } + @Override + public Map serialize() { + Map serialised = new HashMap<>(); + serialised.put("name", this.name); + serialised.put("subscribe", this.subscribe); + serialised.put("follow", this.follow); + return serialised; + } + + public static Channel deserialize(Map map) { + Channel channel = new Channel(); + channel.setName((String)map.get("name")); + channel.setSubscribe((Options)map.get("subscribe")); + channel.setFollow((Options)map.get("follow")); + return channel; + } } diff --git a/src/main/java/me/okx/twitchsync/data/Options.java b/src/main/java/me/okx/twitchsync/data/Options.java index 6e8c40d..e5cf80b 100644 --- a/src/main/java/me/okx/twitchsync/data/Options.java +++ b/src/main/java/me/okx/twitchsync/data/Options.java @@ -1,13 +1,19 @@ package me.okx.twitchsync.data; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import java.util.HashMap; import java.util.List; +import java.util.Map; -public class Options { +@SerializableAs("options") +public class Options implements ConfigurationSerializable { - Boolean enabled; - String rank; - List commands; - List revokeCommands; + private Boolean enabled; + private String rank; + private List commands; + private List revokeCommands; public Boolean getEnabled() { return enabled; @@ -40,4 +46,23 @@ public List getRevokeCommands() { public void setRevokeCommands(List revokeCommands) { this.revokeCommands = revokeCommands; } + + @Override + public Map serialize() { + Map serialised = new HashMap<>(); + serialised.put("enabled", this.enabled); + serialised.put("rank", this.rank); + serialised.put("commands", this.commands); + serialised.put("revoke-commands", this.revokeCommands); + return serialised; + } + + public static Options deserialize(Map map) { + Options options = new Options(); + options.setEnabled((Boolean)map.get("enabled")); + options.setRank((String)map.get("rank")); + options.setCommands((List)map.get("commands")); + options.setRevokeCommands((List)map.get("revoke-commands")); + return options; + } } diff --git a/src/main/java/me/okx/twitchsync/data/Token.java b/src/main/java/me/okx/twitchsync/data/Token.java index 5a7b598..80153b2 100644 --- a/src/main/java/me/okx/twitchsync/data/Token.java +++ b/src/main/java/me/okx/twitchsync/data/Token.java @@ -22,4 +22,8 @@ public Token(String id, AccessToken accessToken) { public void setAccessToken(AccessToken token) { this.accessToken = token; } + + public void setId(String userId) { + this.id = userId; + } } diff --git a/src/main/java/me/okx/twitchsync/data/Upgrade.java b/src/main/java/me/okx/twitchsync/data/Upgrade.java index 374ba66..2b721c8 100644 --- a/src/main/java/me/okx/twitchsync/data/Upgrade.java +++ b/src/main/java/me/okx/twitchsync/data/Upgrade.java @@ -1,6 +1,13 @@ package me.okx.twitchsync.data; -public class Upgrade { +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import java.util.HashMap; +import java.util.Map; + +@SerializableAs("upgrade") +public class Upgrade implements ConfigurationSerializable { private Integer threshold; private String rank; @@ -25,4 +32,16 @@ public String getRank() { public void setRank(String rank) { this.rank = rank; } + + @Override + public Map serialize() { + Map serialised = new HashMap<>(); + serialised.put("rank", this.rank); + serialised.put("threshold", this.threshold); + return serialised; + } + + public static Upgrade deserialize(Map map) { + return new Upgrade((Integer)map.get("threshold"), (String)map.get("rank")); + } } diff --git a/src/main/java/me/okx/twitchsync/events/PlayerListener.java b/src/main/java/me/okx/twitchsync/events/PlayerListener.java index c4b885f..c191205 100644 --- a/src/main/java/me/okx/twitchsync/events/PlayerListener.java +++ b/src/main/java/me/okx/twitchsync/events/PlayerListener.java @@ -5,7 +5,6 @@ import me.okx.twitchsync.data.OptionSupplier; import me.okx.twitchsync.data.Options; import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -19,17 +18,21 @@ public PlayerListener(TwitchSync plugin) { @EventHandler public void on(PlayerSubscriptionEvent e) { - handle(Channel::getSubscribeOptions, e.getPlayer(), e.getChannel()); + + plugin.debug("got new subscribe event", "Event"); + handle(Channel::getSubscribe, e.getPlayer(), e.getChannel()); } @EventHandler public void on(PlayerFollowEvent e) { - handle(Channel::getFollowOptions, e.getPlayer(), e.getChannel()); + plugin.debug("got new follow event", "Event"); + handle(Channel::getFollow, e.getPlayer(), e.getChannel()); } private void handle(OptionSupplier optionSupplier, Player player, Channel channel) { Options options = optionSupplier.supply(channel); if(!options.getEnabled()) { + plugin.debug("Event not processed, configuration section disabled.", "Event"); return; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b0d9b96..1e786b4 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,33 +1,44 @@ # The channel name(s) to check for subscription/follows. channels: - - name: kixstar + - ==: channel + name: kixstar subscribe: + ==: options enabled: true rank: kixsub - commands: - - "broadcast %name% has subscribed to %channel%!" + commands: [] revoke-commands: [] follow: + ==: options enabled: false - - name: thegodlynoob + rank: none + commands: [] + revoke-commands: [] + - ==: channel + name: thegodlynoob subscribe: + ==: options enabled: true rank: godlysub - commands: - - "broadcast %name% has subscribed to %channel%!" + commands: [] revoke-commands: [] follow: + ==: options enabled: false + rank: none + commands: [] + revoke-commands: [] upgrades: - - rank: ubersub + - ==: upgrade + rank: ubersub threshold: 2 # The client ID given by Twitch. -client-id: required +client-id: # The client secret given by Twitch. Remember not to tell anyone this! -client-secret: required +client-secret: # Port to host the HTTP server on server-port: 8080 @@ -40,6 +51,8 @@ expiry-time: 10 # Note that these messages are sent by the web server and use HTML, not chat formatting. messages: + success: "Successfully linked your account." + no-token: "No token for the current user." subscription: no-subscription-program: "The channel does not have a subscription program set up." success: "Successfully set up Twitch Sync." @@ -65,4 +78,4 @@ revoke-interval-days: 1 # Enable debugging information. # If you encounter an unexpected error please enable this # and send the results to the dev! -debug-mode: false +debug-mode: true From 7688a26b3107040daf7dda07c2e75ebd7d323467 Mon Sep 17 00:00:00 2001 From: cfi Date: Thu, 13 Jun 2019 15:51:59 +0200 Subject: [PATCH 6/8] finish refactor --- src/main/java/me/okx/twitchsync/Revoker.java | 36 +--------- .../java/me/okx/twitchsync/TwitchServer.java | 8 ++- .../java/me/okx/twitchsync/Validator.java | 67 +++++++++---------- .../me/okx/twitchsync/data/Persistence.java | 11 +++ .../okx/twitchsync/events/PlayerListener.java | 2 +- .../me/okx/twitchsync/util/SqlHelper.java | 8 +-- 6 files changed, 55 insertions(+), 77 deletions(-) create mode 100644 src/main/java/me/okx/twitchsync/data/Persistence.java diff --git a/src/main/java/me/okx/twitchsync/Revoker.java b/src/main/java/me/okx/twitchsync/Revoker.java index 5aa32cd..02461b4 100644 --- a/src/main/java/me/okx/twitchsync/Revoker.java +++ b/src/main/java/me/okx/twitchsync/Revoker.java @@ -26,44 +26,10 @@ public void run() { long start = System.currentTimeMillis(); Map tokens = plugin.getSqlHelper().getTokens().get(); - checkTokens(tokens); + plugin.getValidator().sync(tokens); long time = System.currentTimeMillis() - start; plugin.getLogger().info("Finished Sync in " + time + "ms."); } - private void checkTokens(Map tokens) { - plugin.getValidator().sync(tokens); - } - - /* - private void revoke(String type, UUID uuid) { - ConfigurationSection section = plugin.getConfig().getConfigurationSection(type); - OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); - - Bukkit.getScheduler().runTask(plugin, () -> { - plugin.debug(type + " - " + uuid, "revoking"); - String group = section.getString("rank"); - if (!group.equalsIgnoreCase("none") && plugin.getPerms() != null) { - plugin.getPerms().playerRemoveGroup(null, player, group); - } - - for (String command : section.getStringList("revoke-commands")) { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command - .replace("%name%", player.getName())); - } - }); - } - */ - - /** - * Check if all states are not YES - */ - public boolean check(Stream states) { - return states.allMatch(stateWithId -> stateWithId.getState() != CheckState.YES); - } - - private AccessToken refresh(AccessToken accessToken) { - return plugin.getValidator().refreshToken(accessToken.getRefreshToken()); - } } diff --git a/src/main/java/me/okx/twitchsync/TwitchServer.java b/src/main/java/me/okx/twitchsync/TwitchServer.java index 447a0c6..499ad7e 100644 --- a/src/main/java/me/okx/twitchsync/TwitchServer.java +++ b/src/main/java/me/okx/twitchsync/TwitchServer.java @@ -40,8 +40,12 @@ public void start() throws IOException { String code = parameters.get("code"); if(state != null && code != null) { - Token token = plugin.getValidator().store(UUID.fromString(state), code).get(); - response = plugin.getValidator().sync(UUID.fromString(state), token).get(); + UUID stateUUID = UUID.fromString(state); + UUID uuid = plugin.getValidator().getUUIDFromAuthState(stateUUID); + if (uuid != null) { + Token token = plugin.getValidator().store(uuid, code).get(); + response = plugin.getValidator().sync(uuid, token).get(); + } else response = SyncResponse.of(SyncMessage.INVALID_URL); } } diff --git a/src/main/java/me/okx/twitchsync/Validator.java b/src/main/java/me/okx/twitchsync/Validator.java index 3a45162..3b56214 100644 --- a/src/main/java/me/okx/twitchsync/Validator.java +++ b/src/main/java/me/okx/twitchsync/Validator.java @@ -175,12 +175,11 @@ private Stream getStates(Token token, Map channel Map headers = new HashMap<>(); headers.put("Client-ID", plugin.getConfig().getString("client-id")); headers.put("Accept", "application/vnd.twitchtv.v5+json"); - headers.put("Authorization", "OAuth " + token.getAccessToken()); + headers.put("Authorization", "OAuth " + token.getAccessToken().getAccessToken()); return channels.entrySet().stream() .map(entry -> getIndividualState(entry.getValue(), headers, - "https://api.twitch.tv/kraken/users/" + token.getId() + "/" + type + "/", entry.getKey())) - .peek(s -> plugin.debug(s, "Peek")); + "https://api.twitch.tv/kraken/users/" + token.getId() + "/" + type + "/", entry.getKey())); } private StateWithId getIndividualState(Channel channel, Map headers, String url, int channelId) { @@ -266,10 +265,10 @@ public CompletableFuture sync(UUID uuid, Token token) { plugin.debug("ID: " + _token.getId(), "Sync"); Stream subscribeStates = getStates(_token, channels, SUBSCRIPTIONS); - List subscriptions = syncStates(uuid, subscribeStates, Channel::getSubscribe); + List subscriptions = syncStates(uuid, subscribeStates, Channel::getSubscribe, SqlHelper::setSubscribed); Stream followStates = getStates(_token, channels, FOLLOWS); - List follows = syncStates(uuid, followStates, Channel::getFollow); + List follows = syncStates(uuid, followStates, Channel::getFollow, SqlHelper::setFollowing); Integer subscriptionCount = subscriptions.size(); // get highest upgrade @@ -280,7 +279,7 @@ public CompletableFuture sync(UUID uuid, Token token) { Permission perms = plugin.getPerms(); for (Upgrade u : upgrades) { - if (u.getThreshold() >= subscriptions.size()) { + if (u.getThreshold() <= subscriptions.size()) { if (!perms.playerInGroup(null, player, u.getRank())) { perms.playerAddGroup(null, player, u.getRank()); } @@ -294,7 +293,7 @@ public CompletableFuture sync(UUID uuid, Token token) { }); } - private List syncStates(UUID uuid, Stream states, OptionSupplier optionSupplier) { + private List syncStates(UUID uuid, Stream states, OptionSupplier optionSupplier, Persistence persistence) { List subscriptions = new ArrayList<>(); SqlHelper helper = plugin.getSqlHelper(); Permission perms = plugin.getPerms(); @@ -302,38 +301,33 @@ private List syncStates(UUID uuid, Stream states, OptionSu Boolean active = null; if (state.getState() == CheckState.YES) active = true; else if (state.getState() == CheckState.NO) active = false; - plugin.debug(uuid.toString() + " - " + state.getChannel().getName() + ": " + state.getState().toString(), "Sync"); + Channel channel = state.getChannel(); + plugin.debug(uuid.toString() + " - " + channel.getName() + ": " + state.getState().toString(), "Sync"); if (active != null) { - helper.setSubscribed(uuid, state.getChannel().getName(), active); - OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); - Options options = optionSupplier.supply(state.getChannel()); - if (active) { - if (!perms.playerInGroup(null, player, options.getRank())) { - if (player.isOnline()) { - plugin.debug("Player is online, running event as normal", "Sync"); - Player onlinePlayer = (Player) player; - Bukkit.getScheduler().runTask(plugin, () -> - Bukkit.getPluginManager().callEvent( - new PlayerSubscriptionEvent(onlinePlayer, state.getChannel()))); - } else { - if (options.getEnabled()) { - if (!options.getRank().equalsIgnoreCase("none") && plugin.getPerms() != null) { - plugin.getPerms().playerAddGroup(null, player, options.getRank()); - } - for (String command : options.getCommands()) { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command - .replace("%name%", player.getName()) - .replace("%channel%", state.getChannel().getName()) - .replace("%channelid%", state.getId() + "")); - } - } + Player player = Bukkit.getPlayer(uuid); + if (player != null && player.isOnline()) { // player is online + Options options = optionSupplier.supply(channel); + Boolean wasActive = helper.isSubscribed(uuid, channel.getName()); + persistence.persist(helper, uuid, channel.getName(), active); + if (!wasActive && active) { + Bukkit.getScheduler().runTask(plugin, () -> + Bukkit.getPluginManager().callEvent( + new PlayerSubscriptionEvent(player, channel))); + } else if (wasActive && !active) { + // remove manually until event is implemented + if (perms.playerInGroup(null, player, options.getRank())) { + perms.playerRemoveGroup(null, player, options.getRank()); + } + + for (String command : options.getRevokeCommands()) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command + .replace("%player%", player.getName()) + .replace("%channel%", channel.getName())); } + } - subscriptions.add(state.getChannel()); } else { - if (perms.playerInGroup(null, player, options.getRank())) { - perms.playerRemoveGroup(null, player, options.getRank()); - } + plugin.debug("Got subscription or follow but player is offline, ignoring.", "Sync"); } } }); @@ -350,4 +344,7 @@ private Token refreshAndSaveToken(UUID uuid, Token token) { } } + public UUID getUUIDFromAuthState(UUID stateUUID) { + return this.userStates.getIfPresent(stateUUID); + } } diff --git a/src/main/java/me/okx/twitchsync/data/Persistence.java b/src/main/java/me/okx/twitchsync/data/Persistence.java new file mode 100644 index 0000000..71a9c89 --- /dev/null +++ b/src/main/java/me/okx/twitchsync/data/Persistence.java @@ -0,0 +1,11 @@ +package me.okx.twitchsync.data; + +import me.okx.twitchsync.util.SqlHelper; + +import java.util.UUID; + +public interface Persistence { + + public void persist(SqlHelper helper, UUID uuid, String channel, Boolean state); + +} diff --git a/src/main/java/me/okx/twitchsync/events/PlayerListener.java b/src/main/java/me/okx/twitchsync/events/PlayerListener.java index c191205..cd7a34d 100644 --- a/src/main/java/me/okx/twitchsync/events/PlayerListener.java +++ b/src/main/java/me/okx/twitchsync/events/PlayerListener.java @@ -43,7 +43,7 @@ private void handle(OptionSupplier optionSupplier, Player player, Channel channe for (String command : options.getCommands()) { Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command - .replace("%name%", player.getName()) + .replace("%player%", player.getName()) .replace("%channel%", channel.getName())); } } diff --git a/src/main/java/me/okx/twitchsync/util/SqlHelper.java b/src/main/java/me/okx/twitchsync/util/SqlHelper.java index f30c86e..21b3ccb 100644 --- a/src/main/java/me/okx/twitchsync/util/SqlHelper.java +++ b/src/main/java/me/okx/twitchsync/util/SqlHelper.java @@ -95,9 +95,9 @@ public Optional> getTokens() { public void setFollowing(UUID uuid, String channel, boolean following) { CompletableFuture.runAsync(() -> { - if (following) { + if (following && !isFollowing(uuid, channel)) { addToTable("following", uuid, channel); - } else { + } else if (!following && isFollowing(uuid, channel)) { deleteFromTable("following", uuid, channel); } }); @@ -105,9 +105,9 @@ public void setFollowing(UUID uuid, String channel, boolean following) { public void setSubscribed(UUID uuid, String channel, boolean subscribed) { CompletableFuture.runAsync(() -> { - if (subscribed) { + if (subscribed && !isSubscribed(uuid, channel)) { addToTable("subscribed", uuid, channel); - } else { + } else if (!subscribed && isSubscribed(uuid, channel)) { deleteFromTable("subscribed", uuid, channel); } }); From d202b702407641d1853e2b7e0db5575205378f61 Mon Sep 17 00:00:00 2001 From: cfi Date: Fri, 14 Jun 2019 15:43:52 +0200 Subject: [PATCH 7/8] update plugin --- .../java/me/okx/twitchsync/TwitchSync.java | 8 ++--- .../java/me/okx/twitchsync/Validator.java | 21 ++++++++++---- .../twitchsync/events/PlayerFollowEvent.java | 14 ++------- .../events/PlayerSubscriptionEvent.java | 16 ++-------- .../me/okx/twitchsync/events/SyncEvent.java | 24 +++++++++++++++ src/main/resources/config.yml | 29 ++++++------------- 6 files changed, 56 insertions(+), 56 deletions(-) create mode 100644 src/main/java/me/okx/twitchsync/events/SyncEvent.java diff --git a/src/main/java/me/okx/twitchsync/TwitchSync.java b/src/main/java/me/okx/twitchsync/TwitchSync.java index 527eece..3551ce7 100644 --- a/src/main/java/me/okx/twitchsync/TwitchSync.java +++ b/src/main/java/me/okx/twitchsync/TwitchSync.java @@ -44,7 +44,7 @@ public void onEnable() { getCommand("twitchsync").setExecutor(new TwitchSyncCommand(this)); getCommand("revoke").setExecutor(new RevokeCommand(this)); - long time = 20*86400*getConfig().getInt("revoke-interval-days"); + long time = 20*60*getConfig().getInt("revoke-interval-minutes"); new Revoker(this).runTaskTimerAsynchronously(this, time, time); new Metrics(this); @@ -92,21 +92,21 @@ public Validator getValidator() { } public T debug(T message) { - if (getConfig().getBoolean("debug-mode") || true) { + if (getConfig().getBoolean("debug-mode")) { getLogger().log(Level.INFO, String.valueOf(message)); } return message; } public T debug(T message, String label) { - if (getConfig().getBoolean("debug-mode") || true) { + if (getConfig().getBoolean("debug-mode")) { getLogger().log(Level.INFO, label + ": " + message); } return message; } public void debug(Throwable throwable) { - if (getConfig().getBoolean("debug-mode") || true) { + if (getConfig().getBoolean("debug-mode")) { throwable.printStackTrace(); } } diff --git a/src/main/java/me/okx/twitchsync/Validator.java b/src/main/java/me/okx/twitchsync/Validator.java index 3b56214..a29b81e 100644 --- a/src/main/java/me/okx/twitchsync/Validator.java +++ b/src/main/java/me/okx/twitchsync/Validator.java @@ -21,7 +21,9 @@ import me.okx.twitchsync.data.json.Users; import me.okx.twitchsync.data.sync.SyncMessage; import me.okx.twitchsync.data.sync.SyncResponse; +import me.okx.twitchsync.events.PlayerFollowEvent; import me.okx.twitchsync.events.PlayerSubscriptionEvent; +import me.okx.twitchsync.events.SyncEvent; import me.okx.twitchsync.util.SqlHelper; import me.okx.twitchsync.util.WebUtil; import net.milkbowl.vault.permission.Permission; @@ -33,12 +35,12 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.text.ParseException; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -265,10 +267,10 @@ public CompletableFuture sync(UUID uuid, Token token) { plugin.debug("ID: " + _token.getId(), "Sync"); Stream subscribeStates = getStates(_token, channels, SUBSCRIPTIONS); - List subscriptions = syncStates(uuid, subscribeStates, Channel::getSubscribe, SqlHelper::setSubscribed); + List subscriptions = syncStates(uuid, subscribeStates, Channel::getSubscribe, SqlHelper::setSubscribed, PlayerSubscriptionEvent.class); Stream followStates = getStates(_token, channels, FOLLOWS); - List follows = syncStates(uuid, followStates, Channel::getFollow, SqlHelper::setFollowing); + List follows = syncStates(uuid, followStates, Channel::getFollow, SqlHelper::setFollowing, PlayerFollowEvent.class); Integer subscriptionCount = subscriptions.size(); // get highest upgrade @@ -293,7 +295,7 @@ public CompletableFuture sync(UUID uuid, Token token) { }); } - private List syncStates(UUID uuid, Stream states, OptionSupplier optionSupplier, Persistence persistence) { + private List syncStates(UUID uuid, Stream states, OptionSupplier optionSupplier, Persistence persistence, Class eventClass) { List subscriptions = new ArrayList<>(); SqlHelper helper = plugin.getSqlHelper(); Permission perms = plugin.getPerms(); @@ -304,6 +306,7 @@ private List syncStates(UUID uuid, Stream states, OptionSu Channel channel = state.getChannel(); plugin.debug(uuid.toString() + " - " + channel.getName() + ": " + state.getState().toString(), "Sync"); if (active != null) { + if (active) subscriptions.add(state.getChannel()); Player player = Bukkit.getPlayer(uuid); if (player != null && player.isOnline()) { // player is online Options options = optionSupplier.supply(channel); @@ -311,8 +314,14 @@ private List syncStates(UUID uuid, Stream states, OptionSu persistence.persist(helper, uuid, channel.getName(), active); if (!wasActive && active) { Bukkit.getScheduler().runTask(plugin, () -> - Bukkit.getPluginManager().callEvent( - new PlayerSubscriptionEvent(player, channel))); + { + try { + SyncEvent event = eventClass.getConstructor(Player.class, Channel.class).newInstance(player, channel); + Bukkit.getPluginManager().callEvent(event); + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + e.printStackTrace(); + } + }); } else if (wasActive && !active) { // remove manually until event is implemented if (perms.playerInGroup(null, player, options.getRank())) { diff --git a/src/main/java/me/okx/twitchsync/events/PlayerFollowEvent.java b/src/main/java/me/okx/twitchsync/events/PlayerFollowEvent.java index 23ffa0e..e408704 100644 --- a/src/main/java/me/okx/twitchsync/events/PlayerFollowEvent.java +++ b/src/main/java/me/okx/twitchsync/events/PlayerFollowEvent.java @@ -3,29 +3,19 @@ import me.okx.twitchsync.data.Channel; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; -import org.bukkit.event.player.PlayerEvent; -public class PlayerFollowEvent extends PlayerEvent { +public class PlayerFollowEvent extends SyncEvent { private static final HandlerList handlers = new HandlerList(); - private Channel channel; - /** * Fired when a player subscription event is triggered * * @param who The player who subscribed */ public PlayerFollowEvent(Player who, Channel channel) { - super(who); - this.channel = channel; + super(who, channel); } - /** - * @return The channel ID the user has subscribed to. - */ - public Channel getChannel() { - return channel; - } @Override public HandlerList getHandlers() { diff --git a/src/main/java/me/okx/twitchsync/events/PlayerSubscriptionEvent.java b/src/main/java/me/okx/twitchsync/events/PlayerSubscriptionEvent.java index 0bb46a0..b4d8c41 100644 --- a/src/main/java/me/okx/twitchsync/events/PlayerSubscriptionEvent.java +++ b/src/main/java/me/okx/twitchsync/events/PlayerSubscriptionEvent.java @@ -1,31 +1,19 @@ package me.okx.twitchsync.events; import me.okx.twitchsync.data.Channel; -import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; -import org.bukkit.event.player.PlayerEvent; -public class PlayerSubscriptionEvent extends PlayerEvent { +public class PlayerSubscriptionEvent extends SyncEvent { private static final HandlerList handlers = new HandlerList(); - private Channel channel; - /** * Fired when a player subscription event is triggered * * @param who The player who subscribed */ public PlayerSubscriptionEvent(Player who, Channel channel) { - super(who); - this.channel = channel; - } - - /** - * @return The channel ID the user has subscribed to. - */ - public Channel getChannel() { - return channel; + super(who, channel); } @Override diff --git a/src/main/java/me/okx/twitchsync/events/SyncEvent.java b/src/main/java/me/okx/twitchsync/events/SyncEvent.java new file mode 100644 index 0000000..d555ca0 --- /dev/null +++ b/src/main/java/me/okx/twitchsync/events/SyncEvent.java @@ -0,0 +1,24 @@ +package me.okx.twitchsync.events; + +import me.okx.twitchsync.data.Channel; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +public abstract class SyncEvent extends PlayerEvent { + + protected Channel channel; + + public SyncEvent(Player who, Channel channel) { + super(who); + this.channel = channel; + } + + /** + * @return The channel ID the user has subscribed to. + */ + public Channel getChannel() { + return channel; + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1e786b4..29e1103 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,27 +1,15 @@ # The channel name(s) to check for subscription/follows. channels: - ==: channel - name: kixstar + name: example subscribe: ==: options enabled: true - rank: kixsub - commands: [] - revoke-commands: [] - follow: - ==: options - enabled: false - rank: none - commands: [] - revoke-commands: [] - - ==: channel - name: thegodlynoob - subscribe: - ==: options - enabled: true - rank: godlysub - commands: [] - revoke-commands: [] + rank: subscriber + commands: + - 'broadcast %player% subscribed to %channel%' + revoke-commands: + - 'w %player% you are no longer subscribed to %channel%.' follow: ==: options enabled: false @@ -73,9 +61,10 @@ messages: # revoke subscriber/follow rewards every X days # this will remove the rank and run revoke-commands -revoke-interval-days: 1 +# defaults to twice a day +revoke-interval-minutes: 720 # Enable debugging information. # If you encounter an unexpected error please enable this # and send the results to the dev! -debug-mode: true +debug-mode: false \ No newline at end of file From e8c2d8022b4493b63003bc4210050b66596d4982 Mon Sep 17 00:00:00 2001 From: cfi Date: Fri, 14 Jun 2019 23:11:13 +0200 Subject: [PATCH 8/8] clean up config --- src/main/resources/config.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 29e1103..82eef07 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -6,6 +6,7 @@ channels: ==: options enabled: true rank: subscriber + # %player% for the player name, %channel% for the channel name (as seen above) commands: - 'broadcast %player% subscribed to %channel%' revoke-commands: @@ -14,12 +15,16 @@ channels: ==: options enabled: false rank: none - commands: [] - revoke-commands: [] + commands: + - 'broadcast %player% followed to %channel%' + revoke-commands: + - 'w %player% you are no longer following %channel%.' upgrades: - ==: upgrade - rank: ubersub + # rank to assign for this upgrade + rank: someupgrade + # amount of subscriptions needed for this upgrade threshold: 2 # The client ID given by Twitch. @@ -59,7 +64,7 @@ messages: invalid-url: "The URL is invalid" unknown-error: "An unknown error occurred. Please try again." -# revoke subscriber/follow rewards every X days +# revoke subscriber/follow rewards every X minutes # this will remove the rank and run revoke-commands # defaults to twice a day revoke-interval-minutes: 720