From 0138ca423b60dd541da34401492613a44c464b37 Mon Sep 17 00:00:00 2001 From: Thomas <1688389+Rakambda@users.noreply.github.com> Date: Sat, 20 Apr 2024 17:38:34 +0200 Subject: [PATCH 01/20] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index eb3d98a0..c8d56288 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.2.14 +version=2.2.15 From 9d53e84d034fe573d19bee636e3c6a2dd0f8d1f3 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+Rakambda@users.noreply.github.com> Date: Sun, 21 Apr 2024 20:29:29 +0200 Subject: [PATCH 02/20] Make json deserialization more resilient by ignoring unknown properties --- .../gql/data/types/ClaimCommunityPointsError.java | 2 +- .../gql/data/types/ClaimDropRewardsPayload.java | 2 +- .../gql/gql/data/types/ClaimDropRewardsStatus.java | 5 ----- .../api/gql/gql/data/types/ClaimErrorCode.java | 6 ------ .../data/types/CommunityPointsCommunityGoal.java | 2 +- .../types/CommunityPointsCommunityGoalStatus.java | 8 -------- .../CommunityPointsLastViewedContentByType.java | 2 +- ...ommunityPointsLastViewedContentByTypeAndID.java | 2 +- .../miner/api/gql/gql/data/types/ContentType.java | 6 ------ .../miner/api/gql/gql/data/types/DropCampaign.java | 2 +- .../api/gql/gql/data/types/DropCampaignStatus.java | 7 ------- .../gql/gql/data/types/MakePredictionError.java | 2 +- .../gql/data/types/MakePredictionErrorCode.java | 12 ------------ .../ws/data/message/subtype/LastViewedContent.java | 3 +-- .../miner/util/json/JacksonUtils.java | 6 ++++-- .../gql/gql/GQLApiChannelPointsContextTest.java | 14 ++++++-------- .../gql/gql/GQLApiClaimCommunityPointsTest.java | 3 +-- .../gql/GQLApiDropsPageClaimDropRewardsTest.java | 3 +-- .../miner/api/gql/gql/GQLApiInventoryTest.java | 5 ++--- .../api/gql/gql/GQLApiMakePredictionTest.java | 3 +-- .../miner/prediction/bet/BetPlacerTest.java | 3 +-- .../miner/util/json/JacksonUtilsTest.java | 7 +++---- 22 files changed, 27 insertions(+), 78 deletions(-) delete mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimDropRewardsStatus.java delete mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimErrorCode.java delete mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsCommunityGoalStatus.java delete mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ContentType.java delete mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/DropCampaignStatus.java delete mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/MakePredictionErrorCode.java diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimCommunityPointsError.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimCommunityPointsError.java index e6d51ded..1a8bba64 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimCommunityPointsError.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimCommunityPointsError.java @@ -20,5 +20,5 @@ public class ClaimCommunityPointsError extends GQLType{ @JsonProperty("code") @NotNull - private ClaimErrorCode code; + private String code; } diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimDropRewardsPayload.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimDropRewardsPayload.java index 5fd935a7..6f380a69 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimDropRewardsPayload.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimDropRewardsPayload.java @@ -20,7 +20,7 @@ public class ClaimDropRewardsPayload extends GQLType{ @JsonProperty("isUserAccountConnected") private boolean isUserAccountConnected; @JsonProperty("status") - private ClaimDropRewardsStatus status; + private String status; @JsonProperty("dropType") private TimeBasedDrop dropType; } diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimDropRewardsStatus.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimDropRewardsStatus.java deleted file mode 100644 index ed830ca0..00000000 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimDropRewardsStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types; - -public enum ClaimDropRewardsStatus{ - ELIGIBLE_FOR_ALL -} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimErrorCode.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimErrorCode.java deleted file mode 100644 index 461b9459..00000000 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ClaimErrorCode.java +++ /dev/null @@ -1,6 +0,0 @@ -package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types; - -public enum ClaimErrorCode{ - FORBIDDEN, - NOT_FOUND -} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsCommunityGoal.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsCommunityGoal.java index bbe12465..2ffdd537 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsCommunityGoal.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsCommunityGoal.java @@ -52,7 +52,7 @@ public class CommunityPointsCommunityGoal extends GQLType{ @JsonDeserialize(using = ISO8601ZonedDateTimeDeserializer.class) private ZonedDateTime startedAt; @JsonProperty("status") - private CommunityPointsCommunityGoalStatus status; + private String status; @JsonProperty("title") private String title; @JsonProperty("type") diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsCommunityGoalStatus.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsCommunityGoalStatus.java deleted file mode 100644 index 4b301fbd..00000000 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsCommunityGoalStatus.java +++ /dev/null @@ -1,8 +0,0 @@ -package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types; - -public enum CommunityPointsCommunityGoalStatus{ - STARTED, - FULFILLED, - UNSTARTED, - ENDED -} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsLastViewedContentByType.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsLastViewedContentByType.java index 3a2e157f..d72dfb97 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsLastViewedContentByType.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsLastViewedContentByType.java @@ -21,7 +21,7 @@ @ToString public class CommunityPointsLastViewedContentByType extends GQLType{ @JsonProperty("contentType") - private ContentType contentType; + private String contentType; @JsonProperty("lastViewedAt") @JsonDeserialize(using = ISO8601ZonedDateTimeDeserializer.class) private ZonedDateTime lastViewedAt; diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsLastViewedContentByTypeAndID.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsLastViewedContentByTypeAndID.java index 469f3081..d3bafcee 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsLastViewedContentByTypeAndID.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/CommunityPointsLastViewedContentByTypeAndID.java @@ -26,7 +26,7 @@ public class CommunityPointsLastViewedContentByTypeAndID extends GQLType{ private String contentId; @JsonProperty("contentType") @NotNull - private ContentType contentType; + private String contentType; @JsonProperty("lastViewedAt") @JsonDeserialize(using = ISO8601ZonedDateTimeDeserializer.class) @NotNull diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ContentType.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ContentType.java deleted file mode 100644 index 52cd7714..00000000 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/ContentType.java +++ /dev/null @@ -1,6 +0,0 @@ -package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types; - -public enum ContentType{ - AUTOMATIC_REWARD, - CUSTOM_REWARD -} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/DropCampaign.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/DropCampaign.java index 87582a40..d8717a24 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/DropCampaign.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/DropCampaign.java @@ -67,7 +67,7 @@ public class DropCampaign extends GQLType{ private List timeBasedDrops = new ArrayList<>(); @JsonProperty("status") @Nullable - private DropCampaignStatus status; + private String status; @JsonProperty("self") @Nullable private DropCampaignSelfEdge self; diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/DropCampaignStatus.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/DropCampaignStatus.java deleted file mode 100644 index 2dbbae99..00000000 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/DropCampaignStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types; - -public enum DropCampaignStatus{ - ACTIVE, - EXPIRED, - UPCOMING -} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/MakePredictionError.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/MakePredictionError.java index 5f0a3e6c..27d2dbb8 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/MakePredictionError.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/MakePredictionError.java @@ -20,5 +20,5 @@ public class MakePredictionError extends GQLType{ @JsonProperty("code") @NotNull - private MakePredictionErrorCode code; + private String code; } diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/MakePredictionErrorCode.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/MakePredictionErrorCode.java deleted file mode 100644 index 2d909a6d..00000000 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/MakePredictionErrorCode.java +++ /dev/null @@ -1,12 +0,0 @@ -package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types; - -public enum MakePredictionErrorCode{ - DUPLICATE_TRANSACTION, - EVENT_NOT_ACTIVE, - FORBIDDEN, - MULTIPLE_OUTCOMES, - MUST_ACCEPT_TOS, - NOT_ENOUGH_POINTS, - RATE_LIMITED, - UNKNOWN -} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/ws/data/message/subtype/LastViewedContent.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/ws/data/message/subtype/LastViewedContent.java index 151c3919..4cb603a0 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/ws/data/message/subtype/LastViewedContent.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/ws/data/message/subtype/LastViewedContent.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.ContentType; import fr.rakambda.channelpointsminer.miner.util.json.ISO8601ZonedDateTimeDeserializer; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -19,7 +18,7 @@ public class LastViewedContent{ @JsonProperty("content_type") @NotNull - private ContentType contentType; + private String contentType; @JsonProperty("content_id") @NotNull private String contentId; diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/util/json/JacksonUtils.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/util/json/JacksonUtils.java index 30331294..a8446a69 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/util/json/JacksonUtils.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/util/json/JacksonUtils.java @@ -21,6 +21,8 @@ import static com.fasterxml.jackson.annotation.PropertyAccessor.SETTER; import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_COMMENTS; import static com.fasterxml.jackson.core.json.JsonReadFeature.ALLOW_TRAILING_COMMA; +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES; +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; import static com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS; import static com.fasterxml.jackson.databind.MapperFeature.SORT_PROPERTIES_ALPHABETICALLY; import static lombok.AccessLevel.PRIVATE; @@ -42,8 +44,8 @@ public static JsonMapper getMapper(){ .enable(ALLOW_TRAILING_COMMA) .enable(ACCEPT_CASE_INSENSITIVE_ENUMS) .enable(ALLOW_COMMENTS) - // .disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES) - // .disable(FAIL_ON_UNKNOWN_PROPERTIES) + .disable(FAIL_ON_IGNORED_PROPERTIES) + .disable(FAIL_ON_UNKNOWN_PROPERTIES) .visibility(FIELD, ANY) .visibility(GETTER, NONE) .visibility(SETTER, NONE) diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiChannelPointsContextTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiChannelPointsContextTest.java index 7a9bd37a..302e2cdc 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiChannelPointsContextTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiChannelPointsContextTest.java @@ -30,8 +30,6 @@ import java.time.ZonedDateTime; import java.util.List; import java.util.Map; -import static fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.ContentType.AUTOMATIC_REWARD; -import static fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.ContentType.CUSTOM_REWARD; import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; @@ -63,11 +61,11 @@ void nominal() throws MalformedURLException{ .canRedeemRewardsForFree(false) .lastViewedContent(List.of( CommunityPointsLastViewedContentByType.builder() - .contentType(AUTOMATIC_REWARD) + .contentType("AUTOMATIC_REWARD") .lastViewedAt(ZonedDateTime.of(2021, 10, 5, 20, 59, 11, 67754116, UTC)) .build(), CommunityPointsLastViewedContentByType.builder() - .contentType(CUSTOM_REWARD) + .contentType("CUSTOM_REWARD") .lastViewedAt(ZonedDateTime.of(2021, 10, 5, 20, 59, 11, 67754117, UTC)) .build() )) @@ -156,7 +154,7 @@ void nominal() throws MalformedURLException{ .lastViewedContent(List.of( CommunityPointsLastViewedContentByTypeAndID.builder() .contentId("SINGLE_MESSAGE_BYPASS_SUB_MODE") - .contentType(AUTOMATIC_REWARD) + .contentType("AUTOMATIC_REWARD") .lastViewedAt(ZonedDateTime.of(2021, 10, 6, 19, 50, 35, 443714534, UTC)) .build() )) @@ -200,11 +198,11 @@ void nominalWithClaim(UnirestMock unirest) throws MalformedURLException{ .canRedeemRewardsForFree(false) .lastViewedContent(List.of( CommunityPointsLastViewedContentByType.builder() - .contentType(AUTOMATIC_REWARD) + .contentType("AUTOMATIC_REWARD") .lastViewedAt(ZonedDateTime.of(2021, 10, 5, 20, 59, 11, 67754116, UTC)) .build(), CommunityPointsLastViewedContentByType.builder() - .contentType(CUSTOM_REWARD) + .contentType("CUSTOM_REWARD") .lastViewedAt(ZonedDateTime.of(2021, 10, 5, 20, 59, 11, 67754117, UTC)) .build() )) @@ -296,7 +294,7 @@ void nominalWithClaim(UnirestMock unirest) throws MalformedURLException{ .lastViewedContent(List.of( CommunityPointsLastViewedContentByTypeAndID.builder() .contentId("SINGLE_MESSAGE_BYPASS_SUB_MODE") - .contentType(AUTOMATIC_REWARD) + .contentType("AUTOMATIC_REWARD") .lastViewedAt(ZonedDateTime.of(2021, 10, 6, 19, 50, 35, 443714534, UTC)) .build() )) diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiClaimCommunityPointsTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiClaimCommunityPointsTest.java index 168c3030..1bde5aec 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiClaimCommunityPointsTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiClaimCommunityPointsTest.java @@ -10,7 +10,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.util.Map; -import static fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.ClaimErrorCode.NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) @@ -57,7 +56,7 @@ void nominalNotFound(){ .data(ClaimCommunityPointsData.builder() .claimCommunityPoints(ClaimCommunityPointsPayload.builder() .error(ClaimCommunityPointsError.builder() - .code(NOT_FOUND) + .code("NOT_FOUND") .build()) .build()) .build()) diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiDropsPageClaimDropRewardsTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiDropsPageClaimDropRewardsTest.java index 9019f900..098044ef 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiDropsPageClaimDropRewardsTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiDropsPageClaimDropRewardsTest.java @@ -3,7 +3,6 @@ import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.GQLResponse; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.dropspageclaimdroprewards.DropsPageClaimDropRewardsData; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.ClaimDropRewardsPayload; -import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.ClaimDropRewardsStatus; import fr.rakambda.channelpointsminer.miner.tests.UnirestMock; import fr.rakambda.channelpointsminer.miner.tests.UnirestMockExtension; import org.mockito.junit.jupiter.MockitoExtension; @@ -28,7 +27,7 @@ void nominal(UnirestMock unirest) throws MalformedURLException{ )) .data(DropsPageClaimDropRewardsData.builder() .claimDropRewards(ClaimDropRewardsPayload.builder() - .status(ClaimDropRewardsStatus.ELIGIBLE_FOR_ALL) + .status("ELIGIBLE_FOR_ALL") .isUserAccountConnected(false) .build()) .build()) diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiInventoryTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiInventoryTest.java index 34d91f82..df93e747 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiInventoryTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiInventoryTest.java @@ -8,7 +8,6 @@ import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.DropCampaign; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.DropCampaignACL; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.DropCampaignSelfEdge; -import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.DropCampaignStatus; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.Game; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.Inventory; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.TimeBasedDrop; @@ -57,7 +56,7 @@ void nominal(UnirestMock unirest) throws MalformedURLException{ .endAt(ZonedDateTime.of(2021, 11, 11, 2, 0, 0, 0, UTC)) .imageUrl(new URL("https://image-1")) .name("campaign name 1") - .status(DropCampaignStatus.ACTIVE) + .status("ACTIVE") .self(DropCampaignSelfEdge.builder() .isAccountConnected(true) .build()) @@ -108,7 +107,7 @@ void nominal(UnirestMock unirest) throws MalformedURLException{ .endAt(ZonedDateTime.of(2021, 11, 7, 23, 30, 0, 0, UTC)) .imageUrl(new URL("https://image2")) .name("campaign name 2") - .status(DropCampaignStatus.ACTIVE) + .status("ACTIVE") .self(DropCampaignSelfEdge.builder() .isAccountConnected(false) .build()) diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiMakePredictionTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiMakePredictionTest.java index 3a2925f3..94bf2ddd 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiMakePredictionTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiMakePredictionTest.java @@ -3,7 +3,6 @@ import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.GQLResponse; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.makeprediction.MakePredictionData; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.MakePredictionError; -import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.MakePredictionErrorCode; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.MakePredictionPayload; import fr.rakambda.channelpointsminer.miner.tests.UnirestMockExtension; import org.mockito.junit.jupiter.MockitoExtension; @@ -51,7 +50,7 @@ void errorMakePrediction(){ .data(MakePredictionData.builder() .makePrediction(MakePredictionPayload.builder() .error(MakePredictionError.builder() - .code(MakePredictionErrorCode.NOT_ENOUGH_POINTS) + .code("NOT_ENOUGH_POINTS") .build()) .build()) .build()) diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/prediction/bet/BetPlacerTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/prediction/bet/BetPlacerTest.java index 1ca23d4c..11d41d4c 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/prediction/bet/BetPlacerTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/prediction/bet/BetPlacerTest.java @@ -4,7 +4,6 @@ import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.GQLResponse; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.makeprediction.MakePredictionData; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.MakePredictionError; -import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.MakePredictionErrorCode; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.MakePredictionPayload; import fr.rakambda.channelpointsminer.miner.api.ws.data.message.subtype.Event; import fr.rakambda.channelpointsminer.miner.api.ws.data.message.subtype.EventStatus; @@ -264,7 +263,7 @@ void placeError(){ transactionIdFactory.when(() -> CommonUtils.randomHex(32)).thenReturn(TRANSACTION_ID); when(makePrediction.getError()).thenReturn(MakePredictionError.builder() - .code(MakePredictionErrorCode.NOT_ENOUGH_POINTS) + .code("NOT_ENOUGH_POINTS") .build()); assertDoesNotThrow(() -> tested.placeBet(bettingPrediction)); diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/util/json/JacksonUtilsTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/util/json/JacksonUtilsTest.java index ed8441d8..2dd2b366 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/util/json/JacksonUtilsTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/util/json/JacksonUtilsTest.java @@ -1,7 +1,6 @@ package fr.rakambda.channelpointsminer.miner.util.json; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonMappingException; import fr.rakambda.channelpointsminer.miner.tests.ParallelizableTest; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -12,7 +11,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatCode; @ParallelizableTest class JacksonUtilsTest{ @@ -82,10 +81,10 @@ void readUnmappedField() throws IOException{ } """; - assertThrows(JsonMappingException.class, () -> JacksonUtils.read(content, new TypeReference(){})); + assertThatCode(() -> JacksonUtils.read(content, new TypeReference(){})).doesNotThrowAnyException(); try(var is = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))){ - assertThrows(JsonMappingException.class, () -> JacksonUtils.read(is, new TypeReference(){})); + assertThatCode(() -> JacksonUtils.read(is, new TypeReference(){})).doesNotThrowAnyException(); } } From 28ddf6c288d2bb65ac489dcb697694b871d3e30e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 08:26:38 +0200 Subject: [PATCH 03/20] chore(deps): bump org.xerial:sqlite-jdbc from 3.45.2.0 to 3.45.3.0 (#761) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ef4a324b..3d130b55 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ json-unit-version = "3.2.7" kitteh-irc-version = "9.0.0" hikari-cp-version = "5.1.0" mariadb-version = "3.3.3" -sqlite-version = "3.45.2.0" +sqlite-version = "3.45.3.0" mysql-version = "8.3.0" rerunner-jupiter-version = "2.1.6" flyway-version = "10.11.0" From 708e93e638bfff700242f8b447ff29b044e4e89d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 08:26:55 +0200 Subject: [PATCH 04/20] chore(deps): bump org.springframework.boot from 3.2.4 to 3.2.5 (#760) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d130b55..7f6b2f05 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ shadow-version = "8.1.1" names-version = "0.51.0" jib-version = "3.4.2" git-properties-version = "2.4.1" -springboot-version = "3.2.4" +springboot-version = "3.2.5" springboot-dependencies-version = "1.1.4" test-logger-version = "4.0.0" git-version-plugin-version = "3.0.0" From 8fc87cc4f983269a1a8fc13bf74d4088498d552d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 08:27:10 +0200 Subject: [PATCH 05/20] chore(deps): bump flyway-version from 10.11.0 to 10.11.1 (#759) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f6b2f05..eb20108d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ mariadb-version = "3.3.3" sqlite-version = "3.45.3.0" mysql-version = "8.3.0" rerunner-jupiter-version = "2.1.6" -flyway-version = "10.11.0" +flyway-version = "10.11.1" selenide-version = "7.2.3" lombok-version = "1.18.32" jacocoVersion = "0.8.10" From 68853b168f44737ce03a1e69909b96fe3812ba38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 08:27:26 +0200 Subject: [PATCH 06/20] chore(deps): bump org.apache.commons:commons-text from 1.11.0 to 1.12.0 (#758) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eb20108d..b6e3ed2d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ jackson-version = "2.17.0" jsonschema-generator-version = "4.35.0" httpclient-version = "4.5.14" lang3-version = "3.14.0" -commons-text-version = "1.11.0" +commons-text-version = "1.12.0" jetbrains-annotations-version = "24.1.0" websocket-version = "1.5.6" junitVersion = "5.10.2" From 1e98be7689ccf9daaee68e50b1724b6b59db6156 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 27 Apr 2024 09:18:00 +0200 Subject: [PATCH 07/20] chore(deps): update gradle/actions action to v3.3.2 (#762) --- .github/workflows/build-test-deploy.yml | 12 ++++++------ .github/workflows/manual-docker.yml | 4 ++-- .github/workflows/release.yml | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml index e883b73d..9102e547 100644 --- a/.github/workflows/build-test-deploy.yml +++ b/.github/workflows/build-test-deploy.yml @@ -27,7 +27,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@v3.3.1 + uses: gradle/actions/dependency-submission@v3.3.2 continue-on-error: true build: @@ -45,7 +45,7 @@ jobs: java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Build project run: | ./gradlew \ @@ -86,7 +86,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Run tests run: | @@ -150,7 +150,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Run tests run: | @@ -214,7 +214,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Calculate miner image name id: miner-image-name @@ -244,7 +244,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Calculate viewer image name id: viewer-image-name diff --git a/.github/workflows/manual-docker.yml b/.github/workflows/manual-docker.yml index 6ab4f677..56b8ce14 100644 --- a/.github/workflows/manual-docker.yml +++ b/.github/workflows/manual-docker.yml @@ -17,7 +17,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Calculate miner image name id: miner-image-name @@ -53,7 +53,7 @@ jobs: distribution: 'temurin' java-version: ${{ env.APP_JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Calculate viewer image name id: viewer-image-name diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 360acd36..dadc86b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Push latest on Docker Hub run: | @@ -42,7 +42,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Push latest on Docker Hub run: | @@ -66,7 +66,7 @@ jobs: distribution: 'temurin' java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.3.1 + uses: gradle/actions/setup-gradle@v3.3.2 - name: Build project run: | From 5920fee928b840e8e28babcbac1ba10207a2cba1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:20:32 +0200 Subject: [PATCH 08/20] chore(deps): bump com.gorylenko.gradle-git-properties (#764) Bumps com.gorylenko.gradle-git-properties from 2.4.1 to 2.4.2. --- updated-dependencies: - dependency-name: com.gorylenko.gradle-git-properties dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6e3ed2d..acfff036 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ jacocoVersion = "0.8.10" shadow-version = "8.1.1" names-version = "0.51.0" jib-version = "3.4.2" -git-properties-version = "2.4.1" +git-properties-version = "2.4.2" springboot-version = "3.2.5" springboot-dependencies-version = "1.1.4" test-logger-version = "4.0.0" From f578703bb617f3998bd77ead5844136caf283588 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:20:38 +0200 Subject: [PATCH 09/20] chore(deps): bump com.codeborne:selenide from 7.2.3 to 7.3.1 (#763) Bumps [com.codeborne:selenide](https://github.com/selenide/selenide) from 7.2.3 to 7.3.1. - [Release notes](https://github.com/selenide/selenide/releases) - [Changelog](https://github.com/selenide/selenide/blob/main/CHANGELOG.md) - [Commits](https://github.com/selenide/selenide/compare/v7.2.3...v7.3.1) --- updated-dependencies: - dependency-name: com.codeborne:selenide dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index acfff036..68099a2f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ sqlite-version = "3.45.3.0" mysql-version = "8.3.0" rerunner-jupiter-version = "2.1.6" flyway-version = "10.11.1" -selenide-version = "7.2.3" +selenide-version = "7.3.1" lombok-version = "1.18.32" jacocoVersion = "0.8.10" From 6b6e2db8d65f1c8885b19e313845cab7d48104a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 4 May 2024 08:19:30 +0200 Subject: [PATCH 10/20] chore(deps): update codecov/codecov-action action to v4.3.1 (#765) --- .github/workflows/build-test-deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml index 9102e547..925c7fe2 100644 --- a/.github/workflows/build-test-deploy.yml +++ b/.github/workflows/build-test-deploy.yml @@ -105,7 +105,7 @@ jobs: with: report_paths: 'miner/build/test-results/test/*.xml' - name: Publish coverage on CodeCov - uses: codecov/codecov-action@v4.3.0 + uses: codecov/codecov-action@v4.3.1 if: always() with: token: ${{ secrets.CODECOV_TOKEN }} @@ -169,7 +169,7 @@ jobs: with: report_paths: 'viewer/build/test-results/test/*.xml' - name: Publish coverage on CodeCov - uses: codecov/codecov-action@v4.3.0 + uses: codecov/codecov-action@v4.3.1 if: always() with: token: ${{ secrets.CODECOV_TOKEN }} From b1011828da8a7007c3c637d9f4d205583a14eabb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 07:21:54 +0200 Subject: [PATCH 11/20] chore(deps): bump io.spring.dependency-management from 1.1.4 to 1.1.5 (#769) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68099a2f..2b9b51fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ names-version = "0.51.0" jib-version = "3.4.2" git-properties-version = "2.4.2" springboot-version = "3.2.5" -springboot-dependencies-version = "1.1.4" +springboot-dependencies-version = "1.1.5" test-logger-version = "4.0.0" git-version-plugin-version = "3.0.0" From c31a4b299860860258d7cce73a73d1d0723c3235 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 07:22:11 +0200 Subject: [PATCH 12/20] chore(deps): bump flyway-version from 10.11.1 to 10.12.0 (#768) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2b9b51fc..373207f1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ mariadb-version = "3.3.3" sqlite-version = "3.45.3.0" mysql-version = "8.3.0" rerunner-jupiter-version = "2.1.6" -flyway-version = "10.11.1" +flyway-version = "10.12.0" selenide-version = "7.3.1" lombok-version = "1.18.32" jacocoVersion = "0.8.10" From d9e415aa87e24da9ed9f32d8397966f6b2a98ba9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 07:22:47 +0200 Subject: [PATCH 13/20] chore(deps): bump com.mysql:mysql-connector-j from 8.3.0 to 8.4.0 (#767) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 373207f1..ca0c3fba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ kitteh-irc-version = "9.0.0" hikari-cp-version = "5.1.0" mariadb-version = "3.3.3" sqlite-version = "3.45.3.0" -mysql-version = "8.3.0" +mysql-version = "8.4.0" rerunner-jupiter-version = "2.1.6" flyway-version = "10.12.0" selenide-version = "7.3.1" From a5535663b8ea04fea9de9d1d73a8da4fc1384ac2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 07:23:07 +0200 Subject: [PATCH 14/20] chore(deps): bump com.fasterxml.jackson:jackson-bom (#766) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca0c3fba..75f2615b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ log4j2-version = "2.23.1" log4j2-slf4j-version = "2.23.1" unirest-version = "4.2.9" picocli-version = "4.7.5" -jackson-version = "2.17.0" +jackson-version = "2.17.1" jsonschema-generator-version = "4.35.0" httpclient-version = "4.5.14" lang3-version = "3.14.0" From 869c57b9972dd79622ef4e8f66e684be4aa1f6a3 Mon Sep 17 00:00:00 2001 From: Thomas <1688389+Rakambda@users.noreply.github.com> Date: Wed, 8 May 2024 21:42:33 +0200 Subject: [PATCH 15/20] Update LeastPointsOutcomePicker.java --- .../miner/prediction/bet/outcome/LeastPointsOutcomePicker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/prediction/bet/outcome/LeastPointsOutcomePicker.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/prediction/bet/outcome/LeastPointsOutcomePicker.java index 901b1638..ee79cd64 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/prediction/bet/outcome/LeastPointsOutcomePicker.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/prediction/bet/outcome/LeastPointsOutcomePicker.java @@ -22,7 +22,7 @@ @Builder @AllArgsConstructor @Log4j2 -@JsonClassDescription("Choose the outcome with the least points. his is the same as 'the outcome with higher odds'.") +@JsonClassDescription("Choose the outcome with the least points. This is the same as 'the outcome with higher odds'.") public class LeastPointsOutcomePicker implements IOutcomePicker{ @Override @NotNull From 965aa67ec4a21c2847675d0f1e5c7ac679038763 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 07:30:09 +0200 Subject: [PATCH 16/20] chore(deps): update softprops/action-gh-release action to v2.0.5 (#770) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dadc86b8..69d6613b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,7 +73,7 @@ jobs: ./gradlew \ assemble - name: Release - uses: softprops/action-gh-release@v2.0.4 + uses: softprops/action-gh-release@v2.0.5 with: fail_on_unmatched_files: false generate_release_notes: true From 0906db5331d0c0faaacfe428be18932b48bf5517 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 12:21:37 +0200 Subject: [PATCH 17/20] chore(deps): bump info.picocli:picocli from 4.7.5 to 4.7.6 (#771) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75f2615b..1fa9cfb6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ slf4j-version = "2.0.13" log4j2-version = "2.23.1" log4j2-slf4j-version = "2.23.1" unirest-version = "4.2.9" -picocli-version = "4.7.5" +picocli-version = "4.7.6" jackson-version = "2.17.1" jsonschema-generator-version = "4.35.0" httpclient-version = "4.5.14" From 8252437bc672ae1423c20093ba7cf2d9a60342ac Mon Sep 17 00:00:00 2001 From: Thomas <108809129+couchoud-t@users.noreply.github.com> Date: Tue, 14 May 2024 13:37:10 +0200 Subject: [PATCH 18/20] =?UTF-8?q?=F0=9F=94=84=20synced=20file(s)=20with=20?= =?UTF-8?q?RakambdaOrg/rakambda-github-files=20(#772)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: undefined --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d55fe40c..00226766 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,6 +23,12 @@ updates: patterns: - "org.apache.logging.log4j:log4j-core" - "org.apache.logging.log4j:log4j-slf4j2-impl" + google-apis: + patterns: + - "com.google.api-client:google-api-client" + - "com.google.oauth-client:google-oauth-client-jetty" + - "com.google.apis:google-api-services-gmail" + - "com.google.apis:google-api-services-calendar" # Disable while paths can't be ignored as it'd update all 'auto-approve-sync-files.yml', Renovate will handle exclusion # - package-ecosystem: "github-actions" # directory: "/" From c3bbb4436a3a955adf5437efc789d30c0f0f822c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 18:15:28 +0200 Subject: [PATCH 19/20] chore(deps): bump flyway-version from 10.12.0 to 10.13.0 (#773) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1fa9cfb6..3c30a819 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ mariadb-version = "3.3.3" sqlite-version = "3.45.3.0" mysql-version = "8.4.0" rerunner-jupiter-version = "2.1.6" -flyway-version = "10.12.0" +flyway-version = "10.13.0" selenide-version = "7.3.1" lombok-version = "1.18.32" jacocoVersion = "0.8.10" From e213b590677f7636b4c9761e85305f1bc62bbf2c Mon Sep 17 00:00:00 2001 From: Thomas <1688389+Rakambda@users.noreply.github.com> Date: Wed, 15 May 2024 12:58:07 +0200 Subject: [PATCH 20/20] New method to progress drops (#774) * New way of watching stream * Fix getting last match * Fix getting last match * Revert log changes * Send minutes watched more often * Fix regex for getting chunk * Refactor * Fix manual docker CI * Send both spade & m3u8 * Rename gql operation * Add tests * Refactor * Bump jacoco * Coverage --- .github/workflows/manual-docker.yml | 2 +- gradle/libs.versions.toml | 2 +- .../miner/api/gql/gql/GQLApi.java | 7 + .../PlaybackAccessTokenData.java | 23 ++ .../PlaybackAccessTokenOperation.java | 31 ++ .../miner/api/gql/gql/data/types/GQLType.java | 1 + .../data/types/StreamPlaybackAccessToken.java | 24 ++ .../miner/api/twitch/TwitchApi.java | 64 +++- .../api/ws/data/message/subtype/Summary.java | 5 - .../miner/factory/MinerRunnableFactory.java | 11 +- .../channelpointsminer/miner/miner/Miner.java | 3 +- .../runnable/SendM3u8MinutesWatched.java | 29 ++ .../miner/runnable/SendMinutesWatched.java | 58 ++-- .../runnable/SendSpadeMinutesWatched.java | 50 +++ .../miner/runnable/UpdateStreamInfo.java | 21 ++ .../miner/streamer/Streamer.java | 4 + .../gql/GQLApiPlaybackAccessTokenTest.java | 45 +++ .../miner/api/twitch/TwitchApiTest.java | 150 ++++++++- .../factory/MinerRunnableFactoryTest.java | 15 +- .../runnable/SendM3u8MinutesWatchedTest.java | 66 ++++ .../runnable/SendMinutesWatchedTest.java | 306 ++++++------------ .../runnable/SendSpadeMinutesWatchedTest.java | 163 ++++++++++ .../miner/runnable/UpdateStreamInfoTest.java | 49 ++- .../gql/gql/playbackaccesstoken_success.json | 18 ++ 24 files changed, 871 insertions(+), 276 deletions(-) create mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/playbackaccesstoken/PlaybackAccessTokenData.java create mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/playbackaccesstoken/PlaybackAccessTokenOperation.java create mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/StreamPlaybackAccessToken.java create mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendM3u8MinutesWatched.java create mode 100644 miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendSpadeMinutesWatched.java create mode 100644 miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiPlaybackAccessTokenTest.java create mode 100644 miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendM3u8MinutesWatchedTest.java create mode 100644 miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendSpadeMinutesWatchedTest.java create mode 100644 miner/src/test/resources/api/gql/gql/playbackaccesstoken_success.json diff --git a/.github/workflows/manual-docker.yml b/.github/workflows/manual-docker.yml index 56b8ce14..55dea27c 100644 --- a/.github/workflows/manual-docker.yml +++ b/.github/workflows/manual-docker.yml @@ -51,7 +51,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: ${{ env.APP_JAVA_VERSION }} + java-version: ${{ vars.JAVA_VERSION }} - name: Setup Gradle uses: gradle/actions/setup-gradle@v3.3.2 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3c30a819..f8525261 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ rerunner-jupiter-version = "2.1.6" flyway-version = "10.13.0" selenide-version = "7.3.1" lombok-version = "1.18.32" -jacocoVersion = "0.8.10" +jacocoVersion = "0.8.12" shadow-version = "8.1.1" names-version = "0.51.0" diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApi.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApi.java index 53d45b63..7038d3a6 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApi.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApi.java @@ -17,6 +17,8 @@ import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.dropshighlightserviceavailabledrops.DropsHighlightServiceAvailableDropsOperation; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.dropspageclaimdroprewards.DropsPageClaimDropRewardsData; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.dropspageclaimdroprewards.DropsPageClaimDropRewardsOperation; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.playbackaccesstoken.PlaybackAccessTokenData; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.playbackaccesstoken.PlaybackAccessTokenOperation; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.inventory.InventoryData; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.inventory.InventoryOperation; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.joinraid.JoinRaidData; @@ -186,6 +188,11 @@ public Optional> makePrediction(@NotNull String return postGqlRequest(new MakePredictionOperation(eventId, outcomeId, amount, transactionId)); } + @NotNull + public Optional> playbackAccessToken(@NotNull String login){ + return postGqlRequest(new PlaybackAccessTokenOperation(login)); + } + @NotNull public List allChannelFollows(){ var follows = new ArrayList(); diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/playbackaccesstoken/PlaybackAccessTokenData.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/playbackaccesstoken/PlaybackAccessTokenData.java new file mode 100644 index 00000000..9feafa71 --- /dev/null +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/playbackaccesstoken/PlaybackAccessTokenData.java @@ -0,0 +1,23 @@ +package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.playbackaccesstoken; + +import com.fasterxml.jackson.annotation.JsonProperty; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.StreamPlaybackAccessToken; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.jetbrains.annotations.NotNull; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode +@ToString +public class PlaybackAccessTokenData{ + @JsonProperty("streamPlaybackAccessToken") + @NotNull + private StreamPlaybackAccessToken streamPlaybackAccessToken; +} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/playbackaccesstoken/PlaybackAccessTokenOperation.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/playbackaccesstoken/PlaybackAccessTokenOperation.java new file mode 100644 index 00000000..199d2203 --- /dev/null +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/playbackaccesstoken/PlaybackAccessTokenOperation.java @@ -0,0 +1,31 @@ +package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.playbackaccesstoken; + +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.GQLResponse; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.IGQLOperation; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.PersistedQueryExtension; +import kong.unirest.core.GenericType; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.jetbrains.annotations.NotNull; + +@Getter +@EqualsAndHashCode(callSuper = true) +@ToString +public class PlaybackAccessTokenOperation extends IGQLOperation{ + public PlaybackAccessTokenOperation(@NotNull String login){ + super("PlaybackAccessToken"); + addPersistedQueryExtension(new PersistedQueryExtension(1, "3093517e37e4f4cb48906155bcd894150aef92617939236d2508f3375ab732ce")); + addVariable("isLive", true); + addVariable("isVod", false); + addVariable("login", login); + addVariable("playerType", "picture-by-picture"); + addVariable("vodID", ""); + } + + @Override + @NotNull + public GenericType> getResponseType(){ + return new GenericType<>(){}; + } +} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/GQLType.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/GQLType.java index 3b941a01..474eae6a 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/GQLType.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/GQLType.java @@ -59,6 +59,7 @@ @JsonSubTypes.Type(value = ClaimCommunityMomentPayload.class, name = "ClaimCommunityMomentPayload"), @JsonSubTypes.Type(value = CommunityMoment.class, name = "CommunityMoment"), @JsonSubTypes.Type(value = CommunityPointsEmoteModifier.class, name = "CommunityPointsEmoteModifier"), + @JsonSubTypes.Type(value = StreamPlaybackAccessToken.class, name = "PlaybackAccessToken"), }) @EqualsAndHashCode public abstract class GQLType{ diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/StreamPlaybackAccessToken.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/StreamPlaybackAccessToken.java new file mode 100644 index 00000000..56ccc848 --- /dev/null +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/data/types/StreamPlaybackAccessToken.java @@ -0,0 +1,24 @@ +package fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import lombok.*; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + +@JsonTypeName("PlaybackAccessToken") +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +@ToString +public class StreamPlaybackAccessToken extends GQLType{ + @JsonProperty("signature") + @NotNull + private String signature; + @JsonProperty("value") + @NotNull + private String value; +} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/twitch/TwitchApi.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/twitch/TwitchApi.java index fd8f0469..518fa742 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/twitch/TwitchApi.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/twitch/TwitchApi.java @@ -11,6 +11,8 @@ import java.net.URI; import java.net.URL; import java.util.Base64; +import java.util.Locale; +import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; import static java.nio.charset.StandardCharsets.UTF_8; @@ -20,6 +22,8 @@ public class TwitchApi{ private static final Pattern SETTINGS_URL_PATTERN = Pattern.compile("(https://static.twitchcdn.net/config/settings.*?js|https://assets.twitch.tv/config/settings.*?.js)"); private static final Pattern SPADE_URL_PATTERN = Pattern.compile("\"spade(Url|_url)\":\"(.*?)\""); + private static final Pattern M3U8_STREAM_PATTERN = Pattern.compile("(https://[/\\-.:\\\\,\"=\\w]*m3u8)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern M3U8_CHUNK_PATTERN = Pattern.compile("(https://(video-edge-|[.\\w\\-/]+\\.ttvnw.net)[.\\w\\-/]+\\.ts(\\?[.\\w\\-/=&]+)?)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); private final UnirestInstance unirest; @@ -71,13 +75,29 @@ private Optional extractSpadeFromSettings(@NotNull String content){ @NotNull private Optional extractUrl(@NotNull Pattern pattern, int group, @NotNull String content){ + return extractUrl(pattern, group, content, false); + } + + @NotNull + private Optional extractUrl(@NotNull Pattern pattern, int group, @NotNull String content, boolean last){ var matcher = pattern.matcher(content); - if(!matcher.find()){ + var matched = false; + String foundGroup = null; + + do{ + matched = matcher.find(); + if(matched){ + foundGroup = matcher.group(group); + } + } + while(matched && last); + + if(Objects.isNull(foundGroup)){ return Optional.empty(); } try{ - return Optional.of(URI.create(matcher.group(group)).toURL()); + return Optional.of(URI.create(foundGroup).toURL()); } catch(MalformedURLException e){ log.error("Failed to parse url", e); @@ -102,4 +122,44 @@ public boolean sendPlayerEvents(@NotNull URL spadeUrl, @NotNull PlayerEvent... e return false; } } + + @NotNull + public Optional getM3u8Url(@NotNull String login, @NotNull String signature, @NotNull String value){ + var response = unirest.get("https://usher.ttvnw.net/api/channel/hls/%s.m3u8".formatted(login.toLowerCase(Locale.ROOT))) + .queryString("sig", signature) + .queryString("token", value) + .queryString("cdm", "wv") + .queryString("player_version", "1.22.0") + .queryString("player_type", "pulsar") + .queryString("player_backend", "mediaplayer") + .queryString("playlist_include_framerate", "true") + .queryString("allow_source", "true") + .queryString("transcode_mode", "cbr_v1") + .asString(); + + if(!response.isSuccess()){ + log.error("Failed to get streamer M3U8 content"); + return Optional.empty(); + } + + return extractUrl(M3U8_STREAM_PATTERN, 1, response.getBody(), true); + } + + public boolean openM3u8LastChunk(@NotNull URL m3u8Url){ + var playlistResponse = unirest.get(m3u8Url.toString()).asString(); + + if(!playlistResponse.isSuccess()){ + log.error("Failed to get streamer M3U8 playlist"); + return false; + } + + var chunkUrl = extractUrl(M3U8_CHUNK_PATTERN, 1, playlistResponse.getBody(), true); + if(chunkUrl.isEmpty()){ + log.error("Failed to get streamer M3U8 chunk from playlist"); + return false; + } + + var chunkRequest = unirest.get(chunkUrl.get().toString()).asEmpty(); + return chunkRequest.isSuccess(); + } } diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/ws/data/message/subtype/Summary.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/ws/data/message/subtype/Summary.java index 11cdd795..59a82b87 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/ws/data/message/subtype/Summary.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/api/ws/data/message/subtype/Summary.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import fr.rakambda.channelpointsminer.miner.util.json.ISO8601ZonedDateTimeDeserializer; -import fr.rakambda.channelpointsminer.miner.util.json.UnknownDeserializer; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -30,8 +29,4 @@ public class Summary{ @JsonDeserialize(using = ISO8601ZonedDateTimeDeserializer.class) @Nullable private ZonedDateTime lastSeen; - @JsonProperty("count_by_md") - @Nullable - @JsonDeserialize(using = UnknownDeserializer.class) - private Object countByMd; } diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/factory/MinerRunnableFactory.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/factory/MinerRunnableFactory.java index bbc9586a..39c886d0 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/factory/MinerRunnableFactory.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/factory/MinerRunnableFactory.java @@ -2,7 +2,8 @@ import fr.rakambda.channelpointsminer.miner.event.manager.IEventManager; import fr.rakambda.channelpointsminer.miner.miner.IMiner; -import fr.rakambda.channelpointsminer.miner.runnable.SendMinutesWatched; +import fr.rakambda.channelpointsminer.miner.runnable.SendM3u8MinutesWatched; +import fr.rakambda.channelpointsminer.miner.runnable.SendSpadeMinutesWatched; import fr.rakambda.channelpointsminer.miner.runnable.StreamerConfigurationReload; import fr.rakambda.channelpointsminer.miner.runnable.SyncInventory; import fr.rakambda.channelpointsminer.miner.runnable.UpdateStreamInfo; @@ -19,8 +20,12 @@ public static UpdateStreamInfo createUpdateStreamInfo(@NotNull IMiner miner){ } @NotNull - public static SendMinutesWatched createSendMinutesWatched(@NotNull IMiner miner){ - return new SendMinutesWatched(miner); + public static SendSpadeMinutesWatched createSendSpadeMinutesWatched(@NotNull IMiner miner){ + return new SendSpadeMinutesWatched(miner); + } + @NotNull + public static SendM3u8MinutesWatched createSendM3u8MinutesWatched(@NotNull IMiner miner){ + return new SendM3u8MinutesWatched(miner); } @NotNull diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/miner/Miner.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/miner/Miner.java index 6bc3a8c3..3e96c52c 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/miner/Miner.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/miner/Miner.java @@ -128,7 +128,8 @@ public void start(){ login(); scheduledExecutor.scheduleWithFixedDelay(getUpdateStreamInfo(), 0, 2, MINUTES); - scheduledExecutor.scheduleWithFixedDelay(MinerRunnableFactory.createSendMinutesWatched(this), 0, 1, MINUTES); + scheduledExecutor.scheduleWithFixedDelay(MinerRunnableFactory.createSendSpadeMinutesWatched(this), 0, 1, MINUTES); + scheduledExecutor.scheduleWithFixedDelay(MinerRunnableFactory.createSendM3u8MinutesWatched(this), 0, 15, SECONDS); scheduledExecutor.scheduleAtFixedRate(MinerRunnableFactory.createWebSocketPing(this), 25, 25, SECONDS); scheduledExecutor.scheduleAtFixedRate(syncInventory, 1, 15, MINUTES); diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendM3u8MinutesWatched.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendM3u8MinutesWatched.java new file mode 100644 index 00000000..3e401cb3 --- /dev/null +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendM3u8MinutesWatched.java @@ -0,0 +1,29 @@ +package fr.rakambda.channelpointsminer.miner.runnable; + +import fr.rakambda.channelpointsminer.miner.miner.IMiner; +import fr.rakambda.channelpointsminer.miner.streamer.Streamer; +import lombok.extern.log4j.Log4j2; +import org.jetbrains.annotations.NotNull; +import java.util.Objects; + +@Log4j2 +public class SendM3u8MinutesWatched extends SendMinutesWatched{ + public SendM3u8MinutesWatched(@NotNull IMiner miner){ + super(miner); + } + + @Override + protected String getType(){ + return "M3U8"; + } + + @Override + protected boolean checkStreamer(Streamer streamer){ + return Objects.nonNull(streamer.getM3u8Url()); + } + + @Override + protected boolean send(Streamer streamer){ + return miner.getTwitchApi().openM3u8LastChunk(streamer.getM3u8Url()); + } +} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendMinutesWatched.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendMinutesWatched.java index 2c85803d..5416a31c 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendMinutesWatched.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendMinutesWatched.java @@ -1,8 +1,5 @@ package fr.rakambda.channelpointsminer.miner.runnable; -import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.Game; -import fr.rakambda.channelpointsminer.miner.api.twitch.data.MinuteWatchedEvent; -import fr.rakambda.channelpointsminer.miner.api.twitch.data.MinuteWatchedProperties; import fr.rakambda.channelpointsminer.miner.factory.TimeFactory; import fr.rakambda.channelpointsminer.miner.log.LogContext; import fr.rakambda.channelpointsminer.miner.miner.IMiner; @@ -20,21 +17,26 @@ @Log4j2 @RequiredArgsConstructor -public class SendMinutesWatched implements Runnable{ - private static final String SITE_PLAYER = "site"; - +public abstract class SendMinutesWatched implements Runnable{ @NotNull - private final IMiner miner; + protected final IMiner miner; private final Map lastSend = new ConcurrentHashMap<>(); + protected abstract String getType(); + + protected abstract boolean checkStreamer(Streamer streamer); + + protected abstract boolean send(Streamer streamer); + @Override public void run(){ - log.debug("Sending all minutes watched"); + log.debug("Starting sending {} minutes watched", getType()); + try(var ignored = LogContext.with(miner)){ var toSendMinutesWatched = miner.getStreamers().stream() .filter(Streamer::isStreaming) .filter(streamer -> !streamer.isChatBanned()) - .filter(streamer -> Objects.nonNull(streamer.getSpadeUrl())) + .filter(this::checkStreamer) .map(streamer -> Map.entry(streamer, streamer.getScore(miner))) .sorted(this::compare) .limit(2) @@ -42,18 +44,21 @@ public void run(){ .toList(); for(var streamer : toSendMinutesWatched){ - if(send(streamer)){ - updateWatchedMinutes(streamer); + try(var ignored2 = LogContext.empty().withStreamer(streamer)){ + log.debug("Sending {} minutes watched", getType()); + if(send(streamer)){ + updateWatchedMinutes(streamer); + } + CommonUtils.randomSleep(100, 50); } - CommonUtils.randomSleep(100, 50); } removeLastSend(toSendMinutesWatched); - log.debug("Done all sending minutes watched"); + log.debug("Done sending all {} minutes watched", getType()); } catch(Exception e){ - log.error("Failed to send all minutes watched", e); + log.error("Failed to send all {} minutes watched", getType(), e); } } @@ -65,31 +70,6 @@ private int compare(@NotNull Map.Entry e1, @NotNull Map.Entry return Integer.compare(e1.getKey().getIndex(), e2.getKey().getIndex()); } - private boolean send(Streamer streamer){ - try(var ignored = LogContext.empty().withStreamer(streamer)){ - log.debug("Sending minutes watched"); - var streamId = streamer.getStreamId(); - if(streamId.isEmpty()){ - return false; - } - - var request = MinuteWatchedEvent.builder() - .properties(MinuteWatchedProperties.builder() - .channelId(streamer.getId()) - .channel(streamer.getUsername()) - .broadcastId(streamId.get()) - .player(SITE_PLAYER) - .userId(miner.getTwitchLogin().getUserIdAsInt(miner.getGqlApi())) - .gameId(streamer.getGame().map(Game::getId).orElse(null)) - .game(streamer.getGame().map(Game::getName).orElse(null)) - .live(true) - .build()) - .build(); - - return miner.getTwitchApi().sendPlayerEvents(streamer.getSpadeUrl(), request); - } - } - private void updateWatchedMinutes(@NotNull Streamer streamer){ var now = TimeFactory.now(); var previousUpdate = lastSend.get(streamer.getId()); diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendSpadeMinutesWatched.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendSpadeMinutesWatched.java new file mode 100644 index 00000000..961afa88 --- /dev/null +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/SendSpadeMinutesWatched.java @@ -0,0 +1,50 @@ +package fr.rakambda.channelpointsminer.miner.runnable; + +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.Game; +import fr.rakambda.channelpointsminer.miner.api.twitch.data.MinuteWatchedEvent; +import fr.rakambda.channelpointsminer.miner.api.twitch.data.MinuteWatchedProperties; +import fr.rakambda.channelpointsminer.miner.miner.IMiner; +import fr.rakambda.channelpointsminer.miner.streamer.Streamer; +import lombok.extern.log4j.Log4j2; +import org.jetbrains.annotations.NotNull; +import java.util.Objects; + +@Log4j2 +public class SendSpadeMinutesWatched extends SendMinutesWatched{ + public SendSpadeMinutesWatched(@NotNull IMiner miner){ + super(miner); + } + + @Override + protected String getType(){ + return "Spade"; + } + + @Override + protected boolean checkStreamer(Streamer streamer){ + return Objects.nonNull(streamer.getSpadeUrl()); + } + + @Override + protected boolean send(Streamer streamer){ + var streamId = streamer.getStreamId(); + if(streamId.isEmpty()){ + return false; + } + + var request = MinuteWatchedEvent.builder() + .properties(MinuteWatchedProperties.builder() + .channelId(streamer.getId()) + .channel(streamer.getUsername()) + .broadcastId(streamId.get()) + .player("site") + .userId(miner.getTwitchLogin().getUserIdAsInt(miner.getGqlApi())) + .gameId(streamer.getGame().map(Game::getId).orElse(null)) + .game(streamer.getGame().map(Game::getName).orElse(null)) + .live(true) + .build()) + .build(); + + return miner.getTwitchApi().sendPlayerEvents(streamer.getSpadeUrl(), request); + } +} diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/UpdateStreamInfo.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/UpdateStreamInfo.java index ee08763d..255446b9 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/UpdateStreamInfo.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/runnable/UpdateStreamInfo.java @@ -44,6 +44,7 @@ public void run(@NotNull Streamer streamer){ updateVideoInfo(streamer); updateSpadeUrl(streamer); + updateM3u8Url(streamer); updateBanStatus(streamer); updatePointsContext(streamer); updateCampaigns(streamer); @@ -81,6 +82,26 @@ private void updateSpadeUrl(@NotNull Streamer streamer){ } } + private void updateM3u8Url(@NotNull Streamer streamer){ + log.trace("Updating m3u8 url"); + if(streamer.isStreaming()){ + if(Objects.isNull(streamer.getM3u8Url())){ + var accessToken = miner.getGqlApi().playbackAccessToken(streamer.getUsername()); + if(accessToken.isEmpty()){ + log.warn("Failed to get playback access token for {}", streamer); + return; + } + var token = accessToken.get().getData().getStreamPlaybackAccessToken(); + + miner.getTwitchApi().getM3u8Url(streamer.getUsername(), token.getSignature(), token.getValue()) + .ifPresent(streamer::setM3u8Url); + } + } + else{ + streamer.setM3u8Url(null); + } + } + private void updateBanStatus(@NotNull Streamer streamer){ log.trace("Updating ban status"); if(streamer.isStreaming()){ diff --git a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/streamer/Streamer.java b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/streamer/Streamer.java index 012f1193..84873b94 100644 --- a/miner/src/main/java/fr/rakambda/channelpointsminer/miner/streamer/Streamer.java +++ b/miner/src/main/java/fr/rakambda/channelpointsminer/miner/streamer/Streamer.java @@ -79,6 +79,10 @@ public class Streamer{ private URL spadeUrl; @Getter private boolean chatBanned; + @Nullable + @Setter + @Getter + private URL m3u8Url; public void addWatchedDuration(@NotNull Duration duration){ watchedDuration = watchedDuration.plus(duration); diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiPlaybackAccessTokenTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiPlaybackAccessTokenTest.java new file mode 100644 index 00000000..e6810f51 --- /dev/null +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/gql/gql/GQLApiPlaybackAccessTokenTest.java @@ -0,0 +1,45 @@ +package fr.rakambda.channelpointsminer.miner.api.gql.gql; + +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.GQLResponse; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.playbackaccesstoken.PlaybackAccessTokenData; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.StreamPlaybackAccessToken; +import fr.rakambda.channelpointsminer.miner.tests.UnirestMockExtension; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +@ExtendWith(UnirestMockExtension.class) +class GQLApiPlaybackAccessTokenTest extends AbstractGQLTest{ + private static final String LOGIN = "channel-name"; + + @Test + void nominal(){ + var expected = GQLResponse. builder() + .extensions(Map.of( + "durationMilliseconds", 18, + "operationName", "PlaybackAccessToken", + "requestID", "request-id" + )) + .data(PlaybackAccessTokenData.builder() + .streamPlaybackAccessToken(StreamPlaybackAccessToken.builder() + .value("value") + .signature("signature") + .build()) + .build()) + .build(); + + expectValidRequestOkWithIntegrityOk("api/gql/gql/playbackaccesstoken_success.json"); + + assertThat(tested.playbackAccessToken(LOGIN)).isPresent().get().isEqualTo(expected); + + verifyAll(); + } + + @Override + protected String getValidRequest(){ + return "{\"extensions\":{\"persistedQuery\":{\"sha256Hash\":\"3093517e37e4f4cb48906155bcd894150aef92617939236d2508f3375ab732ce\",\"version\":1}},\"operationName\":\"PlaybackAccessToken\",\"variables\":{\"isLive\":true,\"vodID\":\"\",\"playerType\":\"picture-by-picture\",\"isVod\":false,\"login\":\"%s\"}}".formatted(LOGIN); + } +} \ No newline at end of file diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/twitch/TwitchApiTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/twitch/TwitchApiTest.java index e27fee64..12b7cd61 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/twitch/TwitchApiTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/api/twitch/TwitchApiTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.util.Base64; import static java.nio.charset.StandardCharsets.UTF_8; @@ -42,8 +43,47 @@ class TwitchApiTest{ private static final String SPADE_BODY = "azeazeazeaze\"spade_url\":\"%s\"azeazeaze".formatted(SPADE_URL); private static final String SPADE_STREAMER_BODY = "azeazeazeaze\"spadeUrl\":\"%s\"azeazeaze".formatted(SPADE_URL); private static final String SPADE_BODY_INVALID_FORMAT = "\"spade_url\":\"%s\"".formatted("https://google.com:-80/"); - - private TwitchApi tested; + private static final String M3U8_SIGNATURE = "sig"; + private static final String M3U8_VALUE = "val"; + private static final String M3U8_URL = "https://usher.ttvnw.net/api/channel/hls/%s.m3u8".formatted(CHANNEL_NAME); + private static final String M3U8_BODY = """ + #EXTM3U + #EXT-X-TWITCH-INFO:NODE="video-edge-a.b",MANIFEST-NODE-TYPE="weaver_cluster",MANIFEST-NODE="video-weaver.a",SUPPRESS="false",SERVER-TIME="1.1",TRANSCODESTACK="2023-Transcode-QS-V1",TRANSCODEMODE="cbr_v1",USER-IP="1.1.1.1",SERVING-ID="serv",CLUSTER="clust",ABS="true",VIDEO-SESSION-ID="12345",BROADCAST-ID="12345",STREAM-TIME="1.1",B="false",USER-COUNTRY="AA",MANIFEST-CLUSTER="clust",ORIGIN="orig",C="data",D="false" + #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="480p30",NAME="480p",AUTOSELECT=YES,DEFAULT=YES + #EXT-X-STREAM-INF:BANDWIDTH=1427999,RESOLUTION=852x480,CODECS="avc1.4D401F,mp4a.40.2",VIDEO="480p30",FRAME-RATE=30.000 + https://stream1.m3u8 + #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="360p30",NAME="360p",AUTOSELECT=YES,DEFAULT=YES + #EXT-X-STREAM-INF:BANDWIDTH=630000,RESOLUTION=640x360,CODECS="avc1.4D401F,mp4a.40.2",VIDEO="360p30",FRAME-RATE=30.000 + https://stream2.m3u8 + #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="160p30",NAME="160p",AUTOSELECT=YES,DEFAULT=YES + #EXT-X-STREAM-INF:BANDWIDTH=230000,RESOLUTION=284x160,CODECS="avc1.4D401F,mp4a.40.2",VIDEO="160p30",FRAME-RATE=30.000 + https://stream3.m3u8 + """; + private static final String M3U8_CHUNK_URL = "https://video-edge-stream.test/chunk3.ts"; + private static final String M3U8_PLAYLIST_BODY = """ + #EXTM3U + #EXT-X-VERSION:3 + #EXT-X-TARGETDURATION:6 + #EXT-X-MEDIA-SEQUENCE:2 + #EXT-X-TWITCH-LIVE-SEQUENCE:3 + #EXT-X-TWITCH-ELAPSED-SECS:1.1 + #EXT-X-TWITCH-TOTAL-SECS:2.2 + #EXT-X-DATERANGE:ID="playlist-creation-123",CLASS="timestamp",START-DATE="2024-05-15T09:10:00.427Z",END-ON-NEXT=YES,X-SERVER-TIME="1234.00" + #EXT-X-DATERANGE:ID="playlist-session-123",CLASS="twitch-session",START-DATE="2024-05-15T09:10:00.427Z",END-ON-NEXT=YES,X-TV-TWITCH-SESSIONID="1234" + #EXT-X-DATERANGE:ID="source-123",CLASS="twitch-stream-source",START-DATE="2024-05-15T09:09:00.348Z",END-ON-NEXT=YES,X-TV-TWITCH-STREAM-SOURCE="live" + #EXT-X-DATERANGE:ID="trigger-123",CLASS="twitch-trigger",START-DATE="2024-05-15T09:09:00.348Z",END-ON-NEXT=YES,X-TV-TWITCH-TRIGGER-URL="https://stream3.m3u8" + #EXT-X-PROGRAM-DATE-TIME:2024-05-15T09:09:49.681Z + #EXTINF:4.167,live + https://video-edge-stream.test/chunk1.ts + #EXT-X-PROGRAM-DATE-TIME:2024-05-15T09:09:53.848Z + #EXTINF:4.167,live + https://video-edge-stream.test/chunk2.ts + #EXT-X-PROGRAM-DATE-TIME:2024-05-15T09:09:58.015Z + #EXTINF:4.166,live + %s + """.formatted(M3U8_CHUNK_URL); + + private TwitchApi tested; private URL streamerUrl; private URL spadeUrl; @@ -237,4 +277,110 @@ void getSpadeUrlNoUrl(UnirestMock unirest){ assertThat(tested.getSpadeUrl(streamerUrl)).isEmpty(); } + + @Test + void getM3u8Url(UnirestMock unirest) throws MalformedURLException{ + unirest.expect(GET, M3U8_URL) + .queryString("sig", M3U8_SIGNATURE) + .queryString("token", M3U8_VALUE) + .queryString("cdm", "wv") + .queryString("player_version", "1.22.0") + .queryString("player_type", "pulsar") + .queryString("player_backend", "mediaplayer") + .queryString("playlist_include_framerate", "true") + .queryString("allow_source", "true") + .queryString("transcode_mode", "cbr_v1") + .thenReturn(M3U8_BODY) + .withStatus(200); + + assertThat(tested.getM3u8Url(CHANNEL_NAME, M3U8_SIGNATURE, M3U8_VALUE)).contains(URI.create("https://stream3.m3u8").toURL()); + } + + @Test + void getM3u8UrlNoUrl(UnirestMock unirest){ + unirest.expect(GET, M3U8_URL) + .queryString("sig", M3U8_SIGNATURE) + .queryString("token", M3U8_VALUE) + .queryString("cdm", "wv") + .queryString("player_version", "1.22.0") + .queryString("player_type", "pulsar") + .queryString("player_backend", "mediaplayer") + .queryString("playlist_include_framerate", "true") + .queryString("allow_source", "true") + .queryString("transcode_mode", "cbr_v1") + .thenReturn("") + .withStatus(200); + + assertThat(tested.getM3u8Url(CHANNEL_NAME, M3U8_SIGNATURE, M3U8_VALUE)).isEmpty(); + } + + @Test + void getM3u8UrlError(UnirestMock unirest){ + unirest.expect(GET, M3U8_URL) + .queryString("sig", M3U8_SIGNATURE) + .queryString("token", M3U8_VALUE) + .queryString("cdm", "wv") + .queryString("player_version", "1.22.0") + .queryString("player_type", "pulsar") + .queryString("player_backend", "mediaplayer") + .queryString("playlist_include_framerate", "true") + .queryString("allow_source", "true") + .queryString("transcode_mode", "cbr_v1") + .thenReturn("") + .withStatus(400); + + assertThat(tested.getM3u8Url(CHANNEL_NAME, M3U8_SIGNATURE, M3U8_VALUE)).isEmpty(); + } + + @Test + void getM3u8ChunkUrl(UnirestMock unirest) throws MalformedURLException{ + var url = URI.create("https://stream.test/streamer.m3u8").toURL(); + + unirest.expect(GET, url.toString()) + .thenReturn(M3U8_PLAYLIST_BODY) + .withStatus(200); + + unirest.expect(GET, M3U8_CHUNK_URL) + .thenReturn() + .withStatus(200); + + assertThat(tested.openM3u8LastChunk(url)).isTrue(); + } + + @Test + void getM3u8ChunkUrlNoPlaylist(UnirestMock unirest) throws MalformedURLException{ + var url = URI.create("https://stream.test/streamer.m3u8").toURL(); + + unirest.expect(GET, url.toString()) + .thenReturn(M3U8_PLAYLIST_BODY) + .withStatus(404); + + assertThat(tested.openM3u8LastChunk(url)).isFalse(); + } + + @Test + void getM3u8ChunkUrlNoChunkUrl(UnirestMock unirest) throws MalformedURLException{ + var url = URI.create("https://stream.test/streamer.m3u8").toURL(); + + unirest.expect(GET, url.toString()) + .thenReturn("") + .withStatus(200); + + assertThat(tested.openM3u8LastChunk(url)).isFalse(); + } + + @Test + void getM3u8ChunkUrlChunkError(UnirestMock unirest) throws MalformedURLException{ + var url = URI.create("https://stream.test/streamer.m3u8").toURL(); + + unirest.expect(GET, url.toString()) + .thenReturn(M3U8_PLAYLIST_BODY) + .withStatus(200); + + unirest.expect(GET, M3U8_CHUNK_URL) + .thenReturn() + .withStatus(400); + + assertThat(tested.openM3u8LastChunk(url)).isFalse(); + } } \ No newline at end of file diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/factory/MinerRunnableFactoryTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/factory/MinerRunnableFactoryTest.java index 333b2fce..e5d26656 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/factory/MinerRunnableFactoryTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/factory/MinerRunnableFactoryTest.java @@ -2,7 +2,8 @@ import fr.rakambda.channelpointsminer.miner.event.manager.IEventManager; import fr.rakambda.channelpointsminer.miner.miner.IMiner; -import fr.rakambda.channelpointsminer.miner.runnable.SendMinutesWatched; +import fr.rakambda.channelpointsminer.miner.runnable.SendM3u8MinutesWatched; +import fr.rakambda.channelpointsminer.miner.runnable.SendSpadeMinutesWatched; import fr.rakambda.channelpointsminer.miner.runnable.StreamerConfigurationReload; import fr.rakambda.channelpointsminer.miner.runnable.SyncInventory; import fr.rakambda.channelpointsminer.miner.runnable.UpdateStreamInfo; @@ -31,9 +32,15 @@ void createUpdateStreamInfo(){ } @Test - void createSendMinutesWatched(){ - Assertions.assertThat(MinerRunnableFactory.createSendMinutesWatched(miner)).isNotNull() - .isInstanceOf(SendMinutesWatched.class); + void createSendM3u8MinutesWatched(){ + Assertions.assertThat(MinerRunnableFactory.createSendM3u8MinutesWatched(miner)).isNotNull() + .isInstanceOf(SendM3u8MinutesWatched.class); + } + + @Test + void createSendSpadeMinutesWatched(){ + Assertions.assertThat(MinerRunnableFactory.createSendSpadeMinutesWatched(miner)).isNotNull() + .isInstanceOf(SendSpadeMinutesWatched.class); } @Test diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendM3u8MinutesWatchedTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendM3u8MinutesWatchedTest.java new file mode 100644 index 00000000..27c964e8 --- /dev/null +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendM3u8MinutesWatchedTest.java @@ -0,0 +1,66 @@ +package fr.rakambda.channelpointsminer.miner.runnable; + +import fr.rakambda.channelpointsminer.miner.api.twitch.TwitchApi; +import fr.rakambda.channelpointsminer.miner.miner.IMiner; +import fr.rakambda.channelpointsminer.miner.streamer.Streamer; +import fr.rakambda.channelpointsminer.miner.tests.ParallelizableTest; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import java.net.MalformedURLException; +import java.net.URL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +@ParallelizableTest +@ExtendWith(MockitoExtension.class) +class SendM3u8MinutesWatchedTest { + @InjectMocks + private SendM3u8MinutesWatched tested; + + @Mock + private IMiner miner; + @Mock + private TwitchApi twitchApi; + @Mock + private Streamer streamer; + + private URL m3u8Url; + + @BeforeEach + void setUp() throws MalformedURLException{ + m3u8Url = new URL("https://google.com/"); + + lenient().when(miner.getTwitchApi()).thenReturn(twitchApi); + lenient().when(streamer.getM3u8Url()).thenReturn(m3u8Url); + } + + @Test + void hasType(){ + assertThat(tested.getType()).isNotNull(); + } + + @Test + void sendingMinutesWatched(){ + when(twitchApi.openM3u8LastChunk(m3u8Url)).thenReturn(true); + + assertDoesNotThrow(() -> tested.send(streamer)); + } + + @Test + void checkValid(){ + assertThat(tested.checkStreamer(streamer)).isTrue(); + } + + @Test + void checkInvalid(){ + when(streamer.getM3u8Url()).thenReturn(null); + + assertThat(tested.checkStreamer(streamer)).isFalse(); + } +} \ No newline at end of file diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendMinutesWatchedTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendMinutesWatchedTest.java index 11e8c42e..f6c3c4ab 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendMinutesWatchedTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendMinutesWatchedTest.java @@ -1,16 +1,9 @@ package fr.rakambda.channelpointsminer.miner.runnable; -import fr.rakambda.channelpointsminer.miner.api.gql.gql.GQLApi; -import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.Game; -import fr.rakambda.channelpointsminer.miner.api.passport.TwitchLogin; -import fr.rakambda.channelpointsminer.miner.api.twitch.TwitchApi; -import fr.rakambda.channelpointsminer.miner.api.twitch.data.MinuteWatchedEvent; -import fr.rakambda.channelpointsminer.miner.api.twitch.data.MinuteWatchedProperties; import fr.rakambda.channelpointsminer.miner.factory.TimeFactory; import fr.rakambda.channelpointsminer.miner.miner.IMiner; import fr.rakambda.channelpointsminer.miner.streamer.Streamer; import fr.rakambda.channelpointsminer.miner.tests.ParallelizableTest; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.junit.jupiter.api.BeforeEach; @@ -20,17 +13,17 @@ import java.net.URL; import java.time.Duration; import java.time.Instant; +import java.util.LinkedList; import java.util.List; -import java.util.Optional; +import java.util.Objects; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -39,322 +32,181 @@ class SendMinutesWatchedTest{ private static final String STREAMER_ID = "streamer-id"; private static final String STREAMER_NAME = "streamer-name"; - private static final String STREAM_ID = "stream-id"; - private static final String SITE_PLAYER = "site"; - private static final int USER_ID = 123456789; - private static final String GAME_NAME = "game-name"; - private static final String GAME_ID = "game-id"; private static final Instant NOW = Instant.parse("2021-03-25T18:12:36Z"); private static final int INDEX = 5; - @InjectMocks - private SendMinutesWatched tested; - @Mock private IMiner miner; @Mock - private TwitchApi twitchApi; - @Mock - private GQLApi gqlApi; - @Mock private Streamer streamer; - @Mock - private TwitchLogin twitchLogin; - @Mock - private Game game; - - private URL spadeUrl; @BeforeEach - void setUp() throws MalformedURLException{ - spadeUrl = new URL("https://google.com/"); - - lenient().when(miner.getTwitchApi()).thenReturn(twitchApi); + void setUp(){ lenient().when(miner.getStreamers()).thenReturn(List.of(streamer)); - lenient().when(miner.getTwitchLogin()).thenReturn(twitchLogin); - lenient().when(miner.getGqlApi()).thenReturn(gqlApi); - - lenient().when(twitchLogin.getUserIdAsInt(gqlApi)).thenReturn(USER_ID); lenient().when(streamer.getId()).thenReturn(STREAMER_ID); lenient().when(streamer.getUsername()).thenReturn(STREAMER_NAME); - lenient().when(streamer.getSpadeUrl()).thenReturn(spadeUrl); - lenient().when(streamer.getStreamId()).thenReturn(Optional.of(STREAM_ID)); lenient().when(streamer.isStreaming()).thenReturn(true); lenient().when(streamer.getIndex()).thenReturn(INDEX); - - lenient().when(game.getName()).thenReturn(GAME_NAME); - lenient().when(game.getId()).thenReturn(GAME_ID); } @Test void sendingMinutesWatched(){ - when(streamer.getGame()).thenReturn(Optional.of(game)); - - var expected = MinuteWatchedEvent.builder() - .properties(MinuteWatchedProperties.builder() - .channelId(STREAMER_ID) - .channel(STREAMER_NAME) - .broadcastId(STREAM_ID) - .player(SITE_PLAYER) - .userId(USER_ID) - .gameId(GAME_ID) - .game(GAME_NAME) - .live(true) - .build()) - .build(); + var tested = new Tester(miner, true, true); - when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); - - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); + assertThat(tested.checkCalled).isOne(); + assertThat(tested.sendCalled).isOne(); verify(streamer, never()).addWatchedDuration(any()); } @Test void sendingMinutesWatchedUpdatesMinutesWatched(){ + var tested = new Tester(miner, true, true); + try(var timeFactory = mockStatic(TimeFactory.class)){ timeFactory.when(TimeFactory::now).thenReturn(NOW); - when(streamer.getGame()).thenReturn(Optional.of(game)); - - var expected = MinuteWatchedEvent.builder() - .properties(MinuteWatchedProperties.builder() - .channelId(STREAMER_ID) - .channel(STREAMER_NAME) - .broadcastId(STREAM_ID) - .player(SITE_PLAYER) - .userId(USER_ID) - .game(GAME_NAME) - .gameId(GAME_ID) - .live(true) - .build()) - .build(); - - when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); - - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer, never()).addWatchedDuration(any()); var delta = Duration.ofSeconds(30); timeFactory.when(TimeFactory::now).thenReturn(NOW.plus(delta)); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer).addWatchedDuration(delta); } } @Test void sendingMinutesWatchedUpdatesMinutesWatchedResetIfNotPresentOnARound(){ + var tested = new Tester(miner, true, true); + try(var timeFactory = mockStatic(TimeFactory.class)){ timeFactory.when(TimeFactory::now).thenReturn(NOW); - when(streamer.getGame()).thenReturn(Optional.of(game)); - - var expected = MinuteWatchedEvent.builder() - .properties(MinuteWatchedProperties.builder() - .channelId(STREAMER_ID) - .channel(STREAMER_NAME) - .broadcastId(STREAM_ID) - .player(SITE_PLAYER) - .userId(USER_ID) - .game(GAME_NAME) - .gameId(GAME_ID) - .live(true) - .build()) - .build(); - when(miner.getStreamers()).thenReturn(List.of(streamer)); - when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer, never()).addWatchedDuration(any()); var delta = Duration.ofSeconds(30); timeFactory.when(TimeFactory::now).thenReturn(NOW.plus(delta)); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer).addWatchedDuration(delta); clearInvocations(streamer); timeFactory.when(TimeFactory::now).thenReturn(NOW); when(miner.getStreamers()).thenReturn(List.of()); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer, never()).addWatchedDuration(any()); when(miner.getStreamers()).thenReturn(List.of(streamer)); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer, never()).addWatchedDuration(any()); delta = Duration.ofSeconds(45); timeFactory.when(TimeFactory::now).thenReturn(NOW.plus(delta)); when(miner.getStreamers()).thenReturn(List.of(streamer)); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer).addWatchedDuration(delta); } } @Test void sendingMinutesWatchedDoesNotUpdateMinutesWatchedIfCallFailed(){ + var tested = new Tester(miner, true, true); + try(var timeFactory = mockStatic(TimeFactory.class)){ timeFactory.when(TimeFactory::now).thenReturn(NOW); - when(streamer.getGame()).thenReturn(Optional.of(game)); - - var expected = MinuteWatchedEvent.builder() - .properties(MinuteWatchedProperties.builder() - .channelId(STREAMER_ID) - .channel(STREAMER_NAME) - .broadcastId(STREAM_ID) - .player(SITE_PLAYER) - .userId(USER_ID) - .game(GAME_NAME) - .gameId(GAME_ID) - .live(true) - .build()) - .build(); - - when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); - - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer, never()).addWatchedDuration(any()); - when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(false); + tested.sendResult = false; var delta = Duration.ofSeconds(30); timeFactory.when(TimeFactory::now).thenReturn(NOW.plus(delta)); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); verify(streamer, never()).addWatchedDuration(any()); } } - @Test - void sendingMinutesWatchedNoGameName(){ - when(streamer.getGame()).thenReturn(Optional.of(game)); - when(game.getName()).thenReturn(null); - when(game.getId()).thenReturn(null); - - var expected = MinuteWatchedEvent.builder() - .properties(MinuteWatchedProperties.builder() - .channelId(STREAMER_ID) - .channel(STREAMER_NAME) - .broadcastId(STREAM_ID) - .player(SITE_PLAYER) - .userId(USER_ID) - .live(true) - .build()) - .build(); - - when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); - - assertDoesNotThrow(() -> tested.run()); - - verify(streamer, never()).addWatchedDuration(any()); - } - - @Test - void sendingMinutesWatchedNoGame(){ - var expected = MinuteWatchedEvent.builder() - .properties(MinuteWatchedProperties.builder() - .channelId(STREAMER_ID) - .channel(STREAMER_NAME) - .broadcastId(STREAM_ID) - .player(SITE_PLAYER) - .userId(USER_ID) - .live(true) - .build()) - .build(); - - when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); - - assertDoesNotThrow(() -> tested.run()); - - verify(streamer, never()).addWatchedDuration(any()); - } - @Test void sendingMinutesWatchedNotStreaming(){ - when(streamer.isStreaming()).thenReturn(false); + var tested = new Tester(miner, true, true); - assertDoesNotThrow(() -> tested.run()); - - verify(twitchApi, never()).sendPlayerEvents(any(), any()); - verify(streamer, never()).addWatchedDuration(any()); - } - - @Test - void sendingMinutesWatchedNoStreamId(){ - when(streamer.getStreamId()).thenReturn(Optional.empty()); + when(streamer.isStreaming()).thenReturn(false); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); + assertThat(tested.sendCalled).isZero(); - verify(twitchApi, never()).sendPlayerEvents(any(), any()); verify(streamer, never()).addWatchedDuration(any()); } @Test - void sendingMinutesWatchedNoSpadeUrl(){ - when(streamer.getSpadeUrl()).thenReturn(null); + void sendingMinutesWatchedCheckKo(){ + var tested = new Tester(miner, false, true); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); + assertThat(tested.sendCalled).isZero(); - verify(twitchApi, never()).sendPlayerEvents(any(), any()); verify(streamer, never()).addWatchedDuration(any()); } @Test void sendingMinutesWatchedChatBanned(){ + var tested = new Tester(miner, true, true); + when(streamer.isChatBanned()).thenReturn(true); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); + assertThat(tested.sendCalled).isZero(); - verify(twitchApi, never()).sendPlayerEvents(any(), any()); verify(streamer, never()).addWatchedDuration(any()); } @Test - void sendingMinutesWatchedSeveralStreamers() throws MalformedURLException{ - var spadeUrl2 = new URL("https://google.com/2"); + void sendingMinutesWatchedSeveralStreamers(){ + var tested = new Tester(miner, true, true); var streamer2 = mock(Streamer.class); when(streamer2.getId()).thenReturn("s2"); when(streamer2.getUsername()).thenReturn("sn2"); - when(streamer2.getSpadeUrl()).thenReturn(spadeUrl2); - when(streamer2.getStreamId()).thenReturn(Optional.of(STREAM_ID)); when(streamer2.isStreaming()).thenReturn(true); - when(twitchApi.sendPlayerEvents(any(), any())).thenReturn(true); - when(miner.getStreamers()).thenReturn(List.of(streamer, streamer2)); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); + assertThat(tested.sendCalled).isEqualTo(2); - verify(twitchApi, times(2)).sendPlayerEvents(any(), any()); verify(streamer, never()).addWatchedDuration(any()); verify(streamer2, never()).addWatchedDuration(any()); } @Test void sendingMinutesWatchedMaxTwoStreamers(){ - when(miner.getStreamers()).thenReturn(List.of(streamer, streamer, streamer, streamer)); - - when(twitchApi.sendPlayerEvents(any(), any())).thenReturn(true); + var tested = new Tester(miner, true, true); - assertDoesNotThrow(() -> tested.run()); + when(miner.getStreamers()).thenReturn(List.of(streamer, streamer, streamer, streamer)); - verify(twitchApi, times(2)).sendPlayerEvents(any(), any()); + assertDoesNotThrow(tested::run); + assertThat(tested.sendCalled).isEqualTo(2); } @Test void sendingMinutesWatchedException(){ - when(twitchApi.sendPlayerEvents(any(), any())).thenThrow(new RuntimeException("For tests")); + var tested = new Tester(miner, true, null); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); } @Test void sendingMinutesWatchedBestScores() throws MalformedURLException{ + var tested = new Tester(miner, true, true); + var s1 = mock(Streamer.class); - when(s1.getSpadeUrl()).thenReturn(new URL("https://spade1")); when(s1.isStreaming()).thenReturn(true); when(s1.getScore(miner)).thenReturn(10); @@ -362,13 +214,10 @@ void sendingMinutesWatchedBestScores() throws MalformedURLException{ var s2 = mock(Streamer.class); when(s2.getId()).thenReturn("s2"); when(s2.getUsername()).thenReturn("sn2"); - when(s2.getSpadeUrl()).thenReturn(spade2); - when(s2.getStreamId()).thenReturn(Optional.of("sid2")); when(s2.isStreaming()).thenReturn(true); when(s2.getScore(miner)).thenReturn(100); var s3 = mock(Streamer.class); - when(s3.getSpadeUrl()).thenReturn(new URL("https://spade3")); when(s3.isStreaming()).thenReturn(true); when(s3.getScore(miner)).thenReturn(20); @@ -376,24 +225,20 @@ void sendingMinutesWatchedBestScores() throws MalformedURLException{ var s4 = mock(Streamer.class); when(s4.getId()).thenReturn("s4"); when(s4.getUsername()).thenReturn("sn4"); - when(s4.getSpadeUrl()).thenReturn(spade4); - when(s4.getStreamId()).thenReturn(Optional.of("sid4")); when(s4.isStreaming()).thenReturn(true); when(s4.getScore(miner)).thenReturn(50); when(miner.getStreamers()).thenReturn(List.of(s1, s2, s3, s4)); - when(twitchApi.sendPlayerEvents(any(), any())).thenReturn(true); - - assertDoesNotThrow(() -> tested.run()); - verify(twitchApi).sendPlayerEvents(eq(spade2), any()); - verify(twitchApi).sendPlayerEvents(eq(spade4), any()); + assertDoesNotThrow(tested::run); + assertThat(tested.sentStreamers).containsExactlyInAnyOrder(s2, s4); } @Test void sendingMinutesWatchedBestScoresEqualsPicksIndex() throws MalformedURLException{ + var tested = new Tester(miner, true, true); + var s1 = mock(Streamer.class); - when(s1.getSpadeUrl()).thenReturn(new URL("https://spade1")); when(s1.isStreaming()).thenReturn(true); when(s1.getScore(miner)).thenReturn(10); when(s1.getIndex()).thenReturn(1); @@ -402,14 +247,11 @@ void sendingMinutesWatchedBestScoresEqualsPicksIndex() throws MalformedURLExcept var s2 = mock(Streamer.class); when(s2.getId()).thenReturn("s2"); when(s2.getUsername()).thenReturn("sn2"); - when(s2.getSpadeUrl()).thenReturn(spade2); - when(s2.getStreamId()).thenReturn(Optional.of("sid2")); when(s2.isStreaming()).thenReturn(true); when(s2.getIndex()).thenReturn(0); when(s2.getScore(miner)).thenReturn(10); var s3 = mock(Streamer.class); - when(s3.getSpadeUrl()).thenReturn(new URL("https://spade3")); when(s3.isStreaming()).thenReturn(true); when(s3.getScore(miner)).thenReturn(10); when(s3.getIndex()).thenReturn(25); @@ -418,18 +260,50 @@ void sendingMinutesWatchedBestScoresEqualsPicksIndex() throws MalformedURLExcept var s4 = mock(Streamer.class); when(s4.getId()).thenReturn("s4"); when(s4.getUsername()).thenReturn("sn4"); - when(s4.getSpadeUrl()).thenReturn(spade4); - when(s4.getStreamId()).thenReturn(Optional.of("sid4")); when(s4.isStreaming()).thenReturn(true); when(s4.getIndex()).thenReturn(-5); when(s4.getScore(miner)).thenReturn(10); when(miner.getStreamers()).thenReturn(List.of(s1, s2, s3, s4)); - when(twitchApi.sendPlayerEvents(any(), any())).thenReturn(true); - assertDoesNotThrow(() -> tested.run()); + assertDoesNotThrow(tested::run); + assertThat(tested.sentStreamers).containsExactlyInAnyOrder(s2, s4); + } + + private static class Tester extends SendMinutesWatched{ + private boolean checkResult; + private Boolean sendResult; + + private int checkCalled = 0; + private int sendCalled = 0; + + private List sentStreamers = new LinkedList<>(); - verify(twitchApi).sendPlayerEvents(eq(spade2), any()); - verify(twitchApi).sendPlayerEvents(eq(spade4), any()); + public Tester(IMiner miner, boolean checkResult, Boolean sendResult){ + super(miner); + this.checkResult = checkResult; + this.sendResult = sendResult; + } + + @Override + protected String getType(){ + return "tester"; + } + + @Override + protected boolean checkStreamer(Streamer streamer){ + checkCalled++; + return checkResult; + } + + @Override + protected boolean send(Streamer streamer){ + sendCalled++; + sentStreamers.add(streamer); + if(Objects.isNull(sendResult)){ + throw new IllegalStateException("For tests"); + } + return sendResult; + } } } \ No newline at end of file diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendSpadeMinutesWatchedTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendSpadeMinutesWatchedTest.java new file mode 100644 index 00000000..be4741e5 --- /dev/null +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/SendSpadeMinutesWatchedTest.java @@ -0,0 +1,163 @@ +package fr.rakambda.channelpointsminer.miner.runnable; + +import fr.rakambda.channelpointsminer.miner.api.gql.gql.GQLApi; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.Game; +import fr.rakambda.channelpointsminer.miner.api.passport.TwitchLogin; +import fr.rakambda.channelpointsminer.miner.api.twitch.TwitchApi; +import fr.rakambda.channelpointsminer.miner.api.twitch.data.MinuteWatchedEvent; +import fr.rakambda.channelpointsminer.miner.api.twitch.data.MinuteWatchedProperties; +import fr.rakambda.channelpointsminer.miner.miner.IMiner; +import fr.rakambda.channelpointsminer.miner.streamer.Streamer; +import fr.rakambda.channelpointsminer.miner.tests.ParallelizableTest; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ParallelizableTest +@ExtendWith(MockitoExtension.class) +class SendSpadeMinutesWatchedTest{ + private static final String STREAMER_ID = "streamer-id"; + private static final String STREAMER_NAME = "streamer-name"; + private static final String STREAM_ID = "stream-id"; + private static final String SITE_PLAYER = "site"; + private static final int USER_ID = 123456789; + private static final String GAME_NAME = "game-name"; + private static final String GAME_ID = "game-id"; + + @InjectMocks + private SendSpadeMinutesWatched tested; + + @Mock + private IMiner miner; + @Mock + private TwitchApi twitchApi; + @Mock + private GQLApi gqlApi; + @Mock + private Streamer streamer; + @Mock + private TwitchLogin twitchLogin; + @Mock + private Game game; + + private URL spadeUrl; + + @BeforeEach + void setUp() throws MalformedURLException{ + spadeUrl = new URL("https://google.com/"); + + lenient().when(miner.getTwitchApi()).thenReturn(twitchApi); + lenient().when(miner.getTwitchLogin()).thenReturn(twitchLogin); + lenient().when(miner.getGqlApi()).thenReturn(gqlApi); + + lenient().when(twitchLogin.getUserIdAsInt(gqlApi)).thenReturn(USER_ID); + + lenient().when(streamer.getId()).thenReturn(STREAMER_ID); + lenient().when(streamer.getUsername()).thenReturn(STREAMER_NAME); + lenient().when(streamer.getSpadeUrl()).thenReturn(spadeUrl); + lenient().when(streamer.getStreamId()).thenReturn(Optional.of(STREAM_ID)); + + lenient().when(game.getName()).thenReturn(GAME_NAME); + lenient().when(game.getId()).thenReturn(GAME_ID); + } + + @Test + void hasType(){ + assertThat(tested.getType()).isNotNull(); + } + + @Test + void sendingMinutesWatched(){ + when(streamer.getGame()).thenReturn(Optional.of(game)); + + var expected = MinuteWatchedEvent.builder() + .properties(MinuteWatchedProperties.builder() + .channelId(STREAMER_ID) + .channel(STREAMER_NAME) + .broadcastId(STREAM_ID) + .player(SITE_PLAYER) + .userId(USER_ID) + .gameId(GAME_ID) + .game(GAME_NAME) + .live(true) + .build()) + .build(); + + when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); + + assertThat(tested.send(streamer)).isTrue(); + } + + @Test + void sendingMinutesWatchedNoGameName(){ + when(streamer.getGame()).thenReturn(Optional.of(game)); + when(game.getName()).thenReturn(null); + when(game.getId()).thenReturn(null); + + var expected = MinuteWatchedEvent.builder() + .properties(MinuteWatchedProperties.builder() + .channelId(STREAMER_ID) + .channel(STREAMER_NAME) + .broadcastId(STREAM_ID) + .player(SITE_PLAYER) + .userId(USER_ID) + .live(true) + .build()) + .build(); + + when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); + + assertThat(tested.send(streamer)).isTrue(); + } + + @Test + void sendingMinutesWatchedNoGame(){ + var expected = MinuteWatchedEvent.builder() + .properties(MinuteWatchedProperties.builder() + .channelId(STREAMER_ID) + .channel(STREAMER_NAME) + .broadcastId(STREAM_ID) + .player(SITE_PLAYER) + .userId(USER_ID) + .live(true) + .build()) + .build(); + + when(twitchApi.sendPlayerEvents(spadeUrl, expected)).thenReturn(true); + + assertThat(tested.send(streamer)).isTrue(); + } + + @Test + void sendingMinutesWatchedNoStreamId(){ + when(streamer.getStreamId()).thenReturn(Optional.empty()); + + assertThat(tested.send(streamer)).isFalse(); + + verify(twitchApi, never()).sendPlayerEvents(any(), any()); + } + + @Test + void checkValid(){ + assertThat(tested.checkStreamer(streamer)).isTrue(); + } + + @Test + void checkInvalid(){ + when(streamer.getSpadeUrl()).thenReturn(null); + + assertThat(tested.checkStreamer(streamer)).isFalse(); + } +} \ No newline at end of file diff --git a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/UpdateStreamInfoTest.java b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/UpdateStreamInfoTest.java index 1aa7fe4c..49729260 100644 --- a/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/UpdateStreamInfoTest.java +++ b/miner/src/test/java/fr/rakambda/channelpointsminer/miner/runnable/UpdateStreamInfoTest.java @@ -5,7 +5,9 @@ import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.channelpointscontext.ChannelPointsContextData; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.chatroombanstatus.ChatRoomBanStatusData; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.dropshighlightserviceavailabledrops.DropsHighlightServiceAvailableDropsData; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.playbackaccesstoken.PlaybackAccessTokenData; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.ChatRoomBanStatus; +import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.StreamPlaybackAccessToken; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.types.User; import fr.rakambda.channelpointsminer.miner.api.gql.gql.data.videoplayerstreaminfooverlaychannel.VideoPlayerStreamInfoOverlayChannelData; import fr.rakambda.channelpointsminer.miner.api.passport.TwitchLogin; @@ -44,6 +46,8 @@ class UpdateStreamInfoTest{ private static final String STREAMER_USERNAME = "streamer-username"; private static final String STREAMER_ID = "streamer-id"; private static final String ACCOUNT_ID = "account-id"; + private static final String M3U8_SIGNATURE = "signature"; + private static final String M3U8_VALUE = "value"; @InjectMocks private UpdateStreamInfo tested; @@ -69,18 +73,24 @@ class UpdateStreamInfoTest{ @Mock private GQLResponse gqlResponseChatRoomBanStatus; @Mock + private GQLResponse gqlResponsePlaybackAccessToken; + @Mock private VideoPlayerStreamInfoOverlayChannelData videoPlayerStreamInfoOverlayChannelData; @Mock private ChannelPointsContextData channelPointsContextData; @Mock private ChatRoomBanStatusData chatRoomBanStatusData; + @Mock + private PlaybackAccessTokenData playbackAccessTokenData; private URL spadeUrl; + private URL m3u8Url; private URL streamerUrl; @BeforeEach void setUp() throws MalformedURLException{ spadeUrl = new URL("https://google.com/"); + m3u8Url = new URL("https://google.m3u8/"); streamerUrl = new URL("https://google.com/streamer"); lenient().when(miner.getGqlApi()).thenReturn(gqlApi); @@ -100,6 +110,7 @@ void setUp() throws MalformedURLException{ lenient().when(gqlResponseChannelPoints.getData()).thenReturn(channelPointsContextData); lenient().when(videoPlayerStreamInfoOverlayChannelData.getUser()).thenReturn(user); lenient().when(gqlResponseChatRoomBanStatus.getData()).thenReturn(chatRoomBanStatusData); + lenient().when(gqlResponsePlaybackAccessToken.getData()).thenReturn(playbackAccessTokenData); } @Test @@ -117,11 +128,13 @@ void updateWithDataNotStreaming(){ verify(gqlApi).channelPointsContext(STREAMER_USERNAME); verify(gqlApi, never()).dropsHighlightServiceAvailableDrops(anyString()); verify(gqlApi, never()).chatRoomBanStatus(anyString(), anyString()); + verify(gqlApi, never()).playbackAccessToken(anyString()); verify(twitchApi, never()).getSpadeUrl(any(URL.class)); verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer).setSpadeUrl(null); + verify(streamer).setM3u8Url(null); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer, never()).setChatBanned(anyBoolean()); @@ -144,11 +157,13 @@ void updateWithDataNotStreamingButWasBefore(){ verify(gqlApi).videoPlayerStreamInfoOverlayChannel(STREAMER_USERNAME); verify(gqlApi, never()).dropsHighlightServiceAvailableDrops(anyString()); verify(gqlApi, never()).chatRoomBanStatus(anyString(), anyString()); + verify(gqlApi, never()).playbackAccessToken(anyString()); verify(twitchApi, never()).getSpadeUrl(any()); verify(streamer).setVideoPlayerStreamInfoOverlayChannel(null); verify(streamer).setChannelPointsContext(null); verify(streamer).setSpadeUrl(null); + verify(streamer).setM3u8Url(null); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer).setLastOffline(NOW); @@ -172,11 +187,13 @@ void updateWithNoDataNotStreaming(){ verify(gqlApi).channelPointsContext(STREAMER_USERNAME); verify(gqlApi, never()).dropsHighlightServiceAvailableDrops(anyString()); verify(gqlApi, never()).chatRoomBanStatus(anyString(), anyString()); + verify(gqlApi, never()).playbackAccessToken(anyString()); verify(twitchApi, never()).getSpadeUrl(any(URL.class)); verify(streamer).setVideoPlayerStreamInfoOverlayChannel(null); verify(streamer).setChannelPointsContext(null); verify(streamer).setSpadeUrl(null); + verify(streamer).setM3u8Url(null); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer, never()).setChatBanned(anyBoolean()); @@ -186,12 +203,13 @@ void updateWithNoDataNotStreaming(){ } @Test - void updateWithDataStreamingAndSpadeUrlPresent(){ + void updateWithDataStreamingAndSpadeAndM3u8UrlPresent(){ try(var timeFactory = mockStatic(TimeFactory.class)){ timeFactory.when(TimeFactory::now).thenReturn(NOW); when(streamer.isStreaming()).thenReturn(true); when(streamer.getSpadeUrl()).thenReturn(spadeUrl); + when(streamer.getM3u8Url()).thenReturn(m3u8Url); when(gqlApi.videoPlayerStreamInfoOverlayChannel(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseVideoPlayer)); when(gqlApi.channelPointsContext(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseChannelPoints)); when(gqlApi.chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID)).thenReturn(Optional.of(gqlResponseChatRoomBanStatus)); @@ -202,11 +220,13 @@ void updateWithDataStreamingAndSpadeUrlPresent(){ verify(gqlApi).channelPointsContext(STREAMER_USERNAME); verify(gqlApi).chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID); verify(gqlApi, never()).dropsHighlightServiceAvailableDrops(anyString()); + verify(gqlApi, never()).playbackAccessToken(anyString()); verify(twitchApi, never()).getSpadeUrl(any(URL.class)); verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer, never()).setSpadeUrl(any()); + verify(streamer, never()).setM3u8Url(any()); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer).setChatBanned(false); @@ -222,6 +242,7 @@ void updateWithDataStreamingAndChatBanned(){ when(streamer.isStreaming()).thenReturn(true); when(streamer.getSpadeUrl()).thenReturn(spadeUrl); + when(streamer.getM3u8Url()).thenReturn(m3u8Url); when(gqlApi.videoPlayerStreamInfoOverlayChannel(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseVideoPlayer)); when(gqlApi.channelPointsContext(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseChannelPoints)); when(gqlApi.chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID)).thenReturn(Optional.of(gqlResponseChatRoomBanStatus)); @@ -234,11 +255,13 @@ void updateWithDataStreamingAndChatBanned(){ verify(gqlApi).channelPointsContext(STREAMER_USERNAME); verify(gqlApi).chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID); verify(gqlApi, never()).dropsHighlightServiceAvailableDrops(anyString()); + verify(gqlApi, never()).playbackAccessToken(anyString()); verify(twitchApi, never()).getSpadeUrl(any(URL.class)); verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer, never()).setSpadeUrl(any()); + verify(streamer, never()).setM3u8Url(any()); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer).setChatBanned(true); @@ -248,16 +271,24 @@ void updateWithDataStreamingAndChatBanned(){ } @Test - void updateWithDataStreamingAndSpadeUrlMissing(){ + void updateWithDataStreamingAndSpadeAndM3u8UrlMissing(){ try(var timeFactory = mockStatic(TimeFactory.class)){ timeFactory.when(TimeFactory::now).thenReturn(NOW); when(streamer.isStreaming()).thenReturn(true); when(streamer.getSpadeUrl()).thenReturn(null); + when(streamer.getM3u8Url()).thenReturn(null); when(gqlApi.videoPlayerStreamInfoOverlayChannel(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseVideoPlayer)); when(gqlApi.channelPointsContext(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseChannelPoints)); when(gqlApi.chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID)).thenReturn(Optional.of(gqlResponseChatRoomBanStatus)); + when(gqlApi.playbackAccessToken(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponsePlaybackAccessToken)); when(twitchApi.getSpadeUrl(streamerUrl)).thenReturn(Optional.of(spadeUrl)); + when(twitchApi.getM3u8Url(STREAMER_USERNAME, M3U8_SIGNATURE, M3U8_VALUE)).thenReturn(Optional.of(m3u8Url)); + + var streamPlaybackAccessToken = mock(StreamPlaybackAccessToken.class); + when(playbackAccessTokenData.getStreamPlaybackAccessToken()).thenReturn(streamPlaybackAccessToken); + when(streamPlaybackAccessToken.getSignature()).thenReturn(M3U8_SIGNATURE); + when(streamPlaybackAccessToken.getValue()).thenReturn(M3U8_VALUE); assertDoesNotThrow(() -> tested.run()); @@ -269,6 +300,7 @@ void updateWithDataStreamingAndSpadeUrlMissing(){ verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer).setSpadeUrl(spadeUrl); + verify(streamer).setM3u8Url(m3u8Url); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer).setChatBanned(false); @@ -300,6 +332,7 @@ void updateWithDataStreamingAndSpadeUrlMissingAndNotReturned(){ verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer, never()).setSpadeUrl(any()); + verify(streamer, never()).setM3u8Url(any()); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer).setChatBanned(false); @@ -320,6 +353,7 @@ void updateWithDataStreamingUpdateCampaign(){ when(streamer.isParticipateCampaigns()).thenReturn(true); when(streamer.isStreamingGame()).thenReturn(true); when(streamer.getSpadeUrl()).thenReturn(spadeUrl); + when(streamer.getM3u8Url()).thenReturn(m3u8Url); when(gqlApi.videoPlayerStreamInfoOverlayChannel(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseVideoPlayer)); when(gqlApi.channelPointsContext(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseChannelPoints)); when(gqlApi.dropsHighlightServiceAvailableDrops(STREAMER_ID)).thenReturn(Optional.of(dropsHighlightServiceAvailableDrops)); @@ -331,11 +365,13 @@ void updateWithDataStreamingUpdateCampaign(){ verify(gqlApi).channelPointsContext(STREAMER_USERNAME); verify(gqlApi).dropsHighlightServiceAvailableDrops(STREAMER_ID); verify(gqlApi).chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID); + verify(gqlApi, never()).playbackAccessToken(anyString()); verify(twitchApi, never()).getSpadeUrl(any(URL.class)); verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer, never()).setSpadeUrl(any()); + verify(streamer, never()).setM3u8Url(any()); verify(streamer).setDropsHighlightServiceAvailableDrops(data); verify(streamer).setLastUpdated(NOW); verify(streamer).setChatBanned(false); @@ -353,6 +389,7 @@ void updateWithDataStreamingUpdateCampaignNoResponse(){ when(streamer.isParticipateCampaigns()).thenReturn(true); when(streamer.isStreamingGame()).thenReturn(true); when(streamer.getSpadeUrl()).thenReturn(spadeUrl); + when(streamer.getM3u8Url()).thenReturn(m3u8Url); when(gqlApi.videoPlayerStreamInfoOverlayChannel(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseVideoPlayer)); when(gqlApi.channelPointsContext(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseChannelPoints)); when(gqlApi.dropsHighlightServiceAvailableDrops(STREAMER_ID)).thenReturn(Optional.empty()); @@ -364,11 +401,13 @@ void updateWithDataStreamingUpdateCampaignNoResponse(){ verify(gqlApi).channelPointsContext(STREAMER_USERNAME); verify(gqlApi).dropsHighlightServiceAvailableDrops(STREAMER_ID); verify(gqlApi).chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID); + verify(gqlApi, never()).playbackAccessToken(anyString()); verify(twitchApi, never()).getSpadeUrl(any(URL.class)); verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer, never()).setSpadeUrl(any()); + verify(streamer, never()).setM3u8Url(any()); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer).setChatBanned(false); @@ -386,6 +425,7 @@ void updateWithDataStreamingUpdateCampaignNotStreamingGame(){ when(streamer.isParticipateCampaigns()).thenReturn(true); when(streamer.isStreamingGame()).thenReturn(false); when(streamer.getSpadeUrl()).thenReturn(spadeUrl); + when(streamer.getM3u8Url()).thenReturn(m3u8Url); when(gqlApi.videoPlayerStreamInfoOverlayChannel(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseVideoPlayer)); when(gqlApi.channelPointsContext(STREAMER_USERNAME)).thenReturn(Optional.of(gqlResponseChannelPoints)); when(gqlApi.chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID)).thenReturn(Optional.of(gqlResponseChatRoomBanStatus)); @@ -396,11 +436,13 @@ void updateWithDataStreamingUpdateCampaignNotStreamingGame(){ verify(gqlApi).channelPointsContext(STREAMER_USERNAME); verify(gqlApi).chatRoomBanStatus(STREAMER_ID, ACCOUNT_ID); verify(gqlApi, never()).dropsHighlightServiceAvailableDrops(anyString()); + verify(gqlApi, never()).playbackAccessToken(anyString()); verify(twitchApi, never()).getSpadeUrl(any(URL.class)); verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer, never()).setSpadeUrl(any()); + verify(streamer, never()).setM3u8Url(any()); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer).setChatBanned(false); @@ -427,6 +469,7 @@ void updateSeveral(){ verify(streamer, times(2)).setVideoPlayerStreamInfoOverlayChannel(null); verify(streamer, times(2)).setChannelPointsContext(null); verify(streamer, times(2)).setSpadeUrl(null); + verify(streamer, times(2)).setM3u8Url(null); verify(streamer, times(2)).setDropsHighlightServiceAvailableDrops(null); verify(streamer, times(2)).setLastUpdated(NOW); verify(streamer, never()).setChatBanned(false); @@ -458,6 +501,7 @@ void notUpdatingIfNotNeeded(){ verify(streamer, never()).setVideoPlayerStreamInfoOverlayChannel(any()); verify(streamer, never()).setChannelPointsContext(any()); verify(streamer, never()).setSpadeUrl(any()); + verify(streamer, never()).setM3u8Url(any()); verify(streamer, never()).setDropsHighlightServiceAvailableDrops(any()); verify(streamer, never()).setLastUpdated(any()); verify(streamer, never()).setLastOffline(any()); @@ -487,6 +531,7 @@ void updatingIfNotNeededByManuallyLaunched(){ verify(streamer).setVideoPlayerStreamInfoOverlayChannel(videoPlayerStreamInfoOverlayChannelData); verify(streamer).setChannelPointsContext(channelPointsContextData); verify(streamer).setSpadeUrl(null); + verify(streamer).setM3u8Url(null); verify(streamer).setDropsHighlightServiceAvailableDrops(null); verify(streamer).setLastUpdated(NOW); verify(streamer, never()).setChatBanned(anyBoolean()); diff --git a/miner/src/test/resources/api/gql/gql/playbackaccesstoken_success.json b/miner/src/test/resources/api/gql/gql/playbackaccesstoken_success.json new file mode 100644 index 00000000..d3e2d1f0 --- /dev/null +++ b/miner/src/test/resources/api/gql/gql/playbackaccesstoken_success.json @@ -0,0 +1,18 @@ +{ + "data": { + "streamPlaybackAccessToken": { + "value": "value", + "signature": "signature", + "authorization": { + "isForbidden": false, + "forbiddenReasonCode": "NONE" + }, + "__typename": "PlaybackAccessToken" + } + }, + "extensions": { + "durationMilliseconds": 18, + "operationName": "PlaybackAccessToken", + "requestID": "request-id" + } +} \ No newline at end of file