From 2cd17f516dc67c8852e51f7ac97916114079c5fb Mon Sep 17 00:00:00 2001 From: iProdigy Date: Sat, 18 Jun 2022 01:57:13 -0500 Subject: [PATCH] feat: add helix raids endpoints in open beta (#587) --- .../twitch4j/auth/domain/TwitchScopes.java | 1 + .../github/twitch4j/helix/TwitchHelix.java | 37 +++++++++++++++++++ .../twitch4j/helix/domain/BanUsersResult.java | 5 +++ .../twitch4j/helix/domain/BannedUser.java | 5 +++ .../twitch4j/helix/domain/RaidRequest.java | 27 ++++++++++++++ .../helix/domain/RaidRequestList.java | 18 +++++++++ .../interceptor/TwitchHelixHttpClient.java | 11 ++++++ .../TwitchHelixRateLimitTracker.java | 17 +++++++++ 8 files changed, 121 insertions(+) create mode 100644 rest-helix/src/main/java/com/github/twitch4j/helix/domain/RaidRequest.java create mode 100644 rest-helix/src/main/java/com/github/twitch4j/helix/domain/RaidRequestList.java diff --git a/auth/src/main/java/com/github/twitch4j/auth/domain/TwitchScopes.java b/auth/src/main/java/com/github/twitch4j/auth/domain/TwitchScopes.java index 928e23ba6..88f7375d1 100644 --- a/auth/src/main/java/com/github/twitch4j/auth/domain/TwitchScopes.java +++ b/auth/src/main/java/com/github/twitch4j/auth/domain/TwitchScopes.java @@ -24,6 +24,7 @@ public enum TwitchScopes { HELIX_CHANNEL_POLLS_READ("channel:read:polls"), HELIX_CHANNEL_PREDICTIONS_MANAGE("channel:manage:predictions"), HELIX_CHANNEL_PREDICTIONS_READ("channel:read:predictions"), + HELIX_CHANNEL_RAIDS_MANAGE("channel:manage:raids"), HELIX_CHANNEL_REDEMPTIONS_READ("channel:read:redemptions"), HELIX_CHANNEL_STREAM_KEY_READ("channel:read:stream_key"), HELIX_CHANNEL_SUBSCRIPTIONS_READ("channel:read:subscriptions"), diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/TwitchHelix.java b/rest-helix/src/main/java/com/github/twitch4j/helix/TwitchHelix.java index 385e0d203..335050132 100644 --- a/rest-helix/src/main/java/com/github/twitch4j/helix/TwitchHelix.java +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/TwitchHelix.java @@ -1626,6 +1626,43 @@ HystrixCommand endPrediction( Prediction prediction ); + /** + * Raid another channel by sending the broadcaster’s viewers to the targeted channel. + *

+ * This endpoint triggers a 90-second cooldown before the raid is executed (or the broadcaster can manually press "Raid now"). + * + * @param authToken Broadcaster user access token with the channel:manage:raids scope. + * @param fromBroadcasterId The ID of the broadcaster that’s sending the raiding party. + * @param toBroadcasterId The ID of the broadcaster to raid. + * @return RaidRequestList + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_CHANNEL_RAIDS_MANAGE + */ + @Unofficial // currently in open beta + @RequestLine("POST /raids?from_broadcaster_id={from_broadcaster_id}&to_broadcaster_id={to_broadcaster_id}") + @Headers("Authorization: Bearer {token}") + HystrixCommand startRaid( + @Param("token") String authToken, + @Param("from_broadcaster_id") String fromBroadcasterId, + @Param("to_broadcaster_id") String toBroadcasterId + ); + + /** + * Cancel a pending raid. + *

+ * You can cancel a raid at any point up until the broadcaster clicks Raid Now in the Twitch UX or the 90 seconds countdown expires. + * + * @param authToken Broadcaster user access token with the channel:manage:raids scope. + * @param broadcasterId The ID of the broadcaster that sent the raiding party. + * @return 204 No Content upon a successful raid cancel call + */ + @Unofficial // currently in open beta + @RequestLine("DELETE /raids?broadcaster_id={broadcaster_id}") + @Headers("Authorization: Bearer {token}") + HystrixCommand cancelRaid( + @Param("token") String authToken, + @Param("broadcaster_id") String broadcasterId + ); + /** * Gets games sorted by number of current viewers on Twitch, most popular first. * Using user-token or app-token to increase rate limits. diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/BanUsersResult.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/BanUsersResult.java index 9c0736bff..e60b0d211 100644 --- a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/BanUsersResult.java +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/BanUsersResult.java @@ -28,6 +28,11 @@ public class BanUsersResult { */ private String userId; + /** + * The UTC date and time (in RFC3999 format) when the ban was created. + */ + private Instant createdAt; + /** * The UTC date and time (in RFC3339 format) that the timeout will end. * Is null if the user was banned instead of put in a timeout. diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/BannedUser.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/BannedUser.java index e338a71f7..f9a705283 100644 --- a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/BannedUser.java +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/BannedUser.java @@ -34,6 +34,11 @@ public class BannedUser { */ private Instant expiresAt; + /** + * The UTC date and time (in RFC3999 format) when the ban was created. + */ + private Instant createdAt; + /** * The reason for the ban if provided by the moderator. */ diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/RaidRequest.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/RaidRequest.java new file mode 100644 index 000000000..49def379b --- /dev/null +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/RaidRequest.java @@ -0,0 +1,27 @@ +package com.github.twitch4j.helix.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.time.Instant; + +@Data +@Setter(AccessLevel.PRIVATE) +public class RaidRequest { + + /** + * The UTC date and time, in RFC3339 format, when the raid request was created. + */ + private Instant createdAt; + + /** + * Whether the channel being raided contains mature content. + */ + @Accessors(fluent = true) + @JsonProperty("is_mature") + private Boolean isMature; + +} diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/RaidRequestList.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/RaidRequestList.java new file mode 100644 index 000000000..62cee300e --- /dev/null +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/RaidRequestList.java @@ -0,0 +1,18 @@ +package com.github.twitch4j.helix.domain; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; + +import java.util.List; + +@Data +@Setter(AccessLevel.PRIVATE) +public class RaidRequestList { + + /** + * A list of raids. The list will contain the single raid that this request created. + */ + private List data; + +} diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/interceptor/TwitchHelixHttpClient.java b/rest-helix/src/main/java/com/github/twitch4j/helix/interceptor/TwitchHelixHttpClient.java index 283843ff1..ea6c0cd6a 100644 --- a/rest-helix/src/main/java/com/github/twitch4j/helix/interceptor/TwitchHelixHttpClient.java +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/interceptor/TwitchHelixHttpClient.java @@ -99,6 +99,17 @@ private Response delegatedExecute(Request request, Request.Options options) thro return executeAgainstBucket(clipBucket, () -> client.execute(request, options)); } + // Raids API: startRaid and cancelRaid have a stricter bucket that applies per channel id + if (templatePath.endsWith("/raids")) { + // Obtain the channel id + String param = request.httpMethod() == Request.HttpMethod.POST ? "from_broadcaster_id" : "broadcaster_id"; + String channelId = request.requestTemplate().queries().getOrDefault(param, Collections.emptyList()).iterator().next(); + + // Conform to endpoint-specific bucket + Bucket raidBucket = rateLimitTracker.getRaidsBucket(channelId); + return executeAgainstBucket(raidBucket, () -> client.execute(request, options)); + } + // no endpoint-specific rate limiting was needed; simply perform network request now return client.execute(request, options); } diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/interceptor/TwitchHelixRateLimitTracker.java b/rest-helix/src/main/java/com/github/twitch4j/helix/interceptor/TwitchHelixRateLimitTracker.java index 6ffd868fa..9d64598e3 100644 --- a/rest-helix/src/main/java/com/github/twitch4j/helix/interceptor/TwitchHelixRateLimitTracker.java +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/interceptor/TwitchHelixRateLimitTracker.java @@ -19,6 +19,11 @@ @SuppressWarnings("ConstantConditions") public final class TwitchHelixRateLimitTracker { + /** + * Officially documented rate limit for {@link com.github.twitch4j.helix.TwitchHelix#startRaid(String, String, String)} and {@link com.github.twitch4j.helix.TwitchHelix#cancelRaid(String, String)} + */ + private static final Bandwidth RAIDS_BANDWIDTH = Bandwidth.simple(10, Duration.ofMinutes(10)); + /** * Empirically determined rate limit on helix bans and unbans, per channel */ @@ -44,6 +49,13 @@ public final class TwitchHelixRateLimitTracker { .expireAfterAccess(1, TimeUnit.MINUTES) .build(); + /** + * Raids API: start and cancel raid rate limit buckets per channel + */ + private final Cache raidsByChannelId = Caffeine.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + /** * Moderation API: ban and unban rate limit buckets per channel */ @@ -98,6 +110,11 @@ String getPrimaryBucketKey(@NotNull OAuth2Credential credential) { * Secondary (endpoint-specific) rate limit buckets */ + @NotNull + Bucket getRaidsBucket(@NotNull String channelId) { + return raidsByChannelId.get(channelId, k -> BucketUtils.createBucket(RAIDS_BANDWIDTH)); + } + @NotNull @Unofficial Bucket getModerationBucket(@NotNull String channelId) {