From 70479f7bee5327ffebdce183da8227f46b919871 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 00:40:25 -0500 Subject: [PATCH 01/16] chore: add new scopes --- .../java/com/github/twitch4j/auth/domain/TwitchScopes.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 88f7375d1..a0384bab8 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 @@ -20,6 +20,7 @@ public enum TwitchScopes { HELIX_CHANNEL_EDITORS_READ("channel:read:editors"), HELIX_CHANNEL_GOALS_READ("channel:read:goals"), HELIX_CHANNEL_HYPE_TRAIN_READ("channel:read:hype_train"), + HELIX_CHANNEL_MODS_MANAGE("channel:manage:moderators"), HELIX_CHANNEL_POLLS_MANAGE("channel:manage:polls"), HELIX_CHANNEL_POLLS_READ("channel:read:polls"), HELIX_CHANNEL_PREDICTIONS_MANAGE("channel:manage:predictions"), @@ -29,6 +30,8 @@ public enum TwitchScopes { HELIX_CHANNEL_STREAM_KEY_READ("channel:read:stream_key"), HELIX_CHANNEL_SUBSCRIPTIONS_READ("channel:read:subscriptions"), HELIX_CHANNEL_VIDEOS_MANAGE("channel:manage:videos"), + HELIX_CHANNEL_VIPS_MANAGE("channel:manage:vips"), + HELIX_CHANNEL_VIPS_READ("channel:read:vips"), HELIX_MODERATION_READ("moderation:read"), HELIX_AUTOMOD_MANAGE("moderator:manage:automod"), HELIX_AUTOMOD_SETTINGS_MANAGE("moderator:manage:automod_settings"), @@ -36,8 +39,11 @@ public enum TwitchScopes { HELIX_BANNED_USERS_MANAGE("moderator:manage:banned_users"), HELIX_BLOCKED_TERMS_MANAGE("moderator:manage:blocked_terms"), HELIX_BLOCKED_TERMS_READ("moderator:read:blocked_terms"), + HELIX_CHAT_ANNOUNCEMENTS_MANAGE("moderator:manage:announcements"), + HELIX_CHAT_MESSAGES_MANAGE("moderator:manage:chat_messages"), HELIX_CHAT_SETTINGS_MANAGE("moderator:manage:chat_settings"), HELIX_CHAT_SETTINGS_READ("moderator:read:chat_settings"), + HELIX_USER_COLOR_MANAGE("user:manage:chat_color"), HELIX_USER_EDIT("user:edit"), HELIX_USER_EDIT_BROADCAST("user:edit:broadcast"), HELIX_USER_EDIT_FOLLOWS("user:edit:follows"), @@ -47,6 +53,7 @@ public enum TwitchScopes { HELIX_USER_SUBSCRIPTIONS_READ("user:read:subscriptions"), HELIX_USER_EMAIL("user:read:email"), HELIX_USER_BLOCKS_MANAGE("user:manage:blocked_users"), + HELIX_USER_WHISPERS_MANAGE("user:manage:whispers"), KRAKEN_CHANNEL_CHECK_SUBSCRIPTION("channel_check_subscription"), KRAKEN_CHANNEL_COMMERCIAL("channel_commercial"), KRAKEN_CHANNEL_EDITOR("channel_editor"), From 4b1eae01f262c33be85b9c5de2b0ac143ed63b48 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 00:54:36 -0500 Subject: [PATCH 02/16] feat: implement TwitchHelix#sendWhisper --- .../github/twitch4j/helix/TwitchHelix.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) 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 e5020bdd7..d35bddfc5 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 @@ -2456,4 +2456,43 @@ HystrixCommand requestWebhookSubscription( WebhookRequest request, // POJO as first arg is assumed by feign to be body if no @Body annotation @Param("token") String authToken ); + + /** + * Sends a whisper message to the specified user. + *

+ * Note: The user sending the whisper must have a verified phone number. + *

+ * Note: The API may silently drop whispers that it suspects of violating Twitch policies. + * (The API does not indicate that it dropped the whisper; it returns a 204 status code as if it succeeded). + *

+ * Rate Limits: You may whisper to a maximum of 40 unique recipients per day. + * Within the per day limit, you may whisper a maximum of 3 whispers per second and a maximum of 100 whispers per minute. + *

+ * The ID in the from_user_id query parameter must match the user ID in the access token. + *

+ * The maximum message lengths are: + *

    + *
  • 500 characters if the user you're sending the message to hasn't whispered you before.
  • + *
  • 10,000 characters if the user you're sending the message to has whispered you before.
  • + *
+ *

+ * Error 400 (Bad Request) can occur if the user that you're sending the whisper to doesn't allow whisper messages from strangers, + * and has not followed the sender's twitch account. + * + * @param authToken User access token for the whisper sender that includes the user:manage:whispers scope + * @param fromUserId The ID of the user sending the whisper. This user must have a verified phone number. + * @param toUserId The ID of the user to receive the whisper. + * @param message The whisper message to send. The message must not be empty. Messages that exceed the maximum length are truncated. + * @return 204 No Content upon a successful call, even if the message was silently dropped + */ + @RequestLine("POST /whispers?from_user_id={from_user_id}&to_user_id={to_user_id}") + @Headers("Authorization: Bearer {token}") + @Body("%7B\"message\":\"{message}\"%7D") + HystrixCommand sendWhisper( + @Param("token") String authToken, + @Param("from_user_id") String fromUserId, + @Param("to_user_id") String toUserId, + @Param(value = "message", expander = JsonStringExpander.class) String message + ); + } From 046883cc569c0f0fe5e1c4e36cce642108fff9b1 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 01:19:13 -0500 Subject: [PATCH 03/16] feat: implement TwitchHelix#sendChatAnnouncement --- .../github/twitch4j/helix/TwitchHelix.java | 44 +++++++++++++++++-- .../helix/domain/AnnouncementColor.java | 17 +++++++ 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 rest-helix/src/main/java/com/github/twitch4j/helix/domain/AnnouncementColor.java 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 d35bddfc5..3e1bbcbf3 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 @@ -13,6 +13,7 @@ import com.netflix.hystrix.HystrixCommand; import feign.*; import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.io.InputStream; @@ -322,6 +323,34 @@ HystrixCommand updateRedemptionStatus( @Param("status") RedemptionStatus newStatus ); + /** + * Sends an announcement to the broadcaster’s chat room. + *

+ * This endpoint is in open beta. + * + * @param authToken User access token (scope: moderator:manage:announcements) of the broadcaster or a moderator. + * @param broadcasterId The ID of the broadcaster that owns the chat room to send the announcement to. + * @param moderatorId The ID of a user who has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the OAuth token, which can be a moderator or the broadcaster. + * @param message The announcement to make in the broadcaster’s chat room. Announcements are limited to a maximum of 500 characters. + * @param color The color used to highlight the announcement. + * @return 204 No Content upon a successful call + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_CHAT_ANNOUNCEMENTS_MANAGE + */ + @Unofficial // beta + @RequestLine("POST /chat/announcements?broadcaster_id={broadcaster_id}&moderator_id={moderator_id}") + @Headers({ + "Authorization: Bearer {token}", + "Content-Type: application/json" + }) + @Body("%7B\"message\":\"{message}\",\"color\":\"{color}\"%7D") + HystrixCommand sendChatAnnouncement( + @Param("token") String authToken, + @NotNull @Param("broadcaster_id") String broadcasterId, + @NotNull @Param("moderator_id") String moderatorId, + @NotNull @Param(value = "message", expander = JsonStringExpander.class) String message, + @NotNull @Param("color") AnnouncementColor color + ); + /** * Gets a list of custom chat badges that can be used in chat for the specified channel. * This includes subscriber badges and Bit badges. @@ -2460,6 +2489,8 @@ HystrixCommand requestWebhookSubscription( /** * Sends a whisper message to the specified user. *

+ * This endpoint is in open beta. + *

* Note: The user sending the whisper must have a verified phone number. *

* Note: The API may silently drop whispers that it suspects of violating Twitch policies. @@ -2484,15 +2515,20 @@ HystrixCommand requestWebhookSubscription( * @param toUserId The ID of the user to receive the whisper. * @param message The whisper message to send. The message must not be empty. Messages that exceed the maximum length are truncated. * @return 204 No Content upon a successful call, even if the message was silently dropped + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_USER_WHISPERS_MANAGE */ + @Unofficial // beta @RequestLine("POST /whispers?from_user_id={from_user_id}&to_user_id={to_user_id}") - @Headers("Authorization: Bearer {token}") + @Headers({ + "Authorization: Bearer {token}", + "Content-Type: application/json" + }) @Body("%7B\"message\":\"{message}\"%7D") HystrixCommand sendWhisper( @Param("token") String authToken, - @Param("from_user_id") String fromUserId, - @Param("to_user_id") String toUserId, - @Param(value = "message", expander = JsonStringExpander.class) String message + @NotNull @Param("from_user_id") String fromUserId, + @NotNull @Param("to_user_id") String toUserId, + @NotNull @Param(value = "message", expander = JsonStringExpander.class) String message ); } diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/AnnouncementColor.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/AnnouncementColor.java new file mode 100644 index 000000000..dba49f140 --- /dev/null +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/AnnouncementColor.java @@ -0,0 +1,17 @@ +package com.github.twitch4j.helix.domain; + +/** + * The color used to highlight the announcement. + */ +public enum AnnouncementColor { + BLUE, + GREEN, + ORANGE, + PURPLE, + PRIMARY; + + @Override + public String toString() { + return this.name().toLowerCase(); + } +} From db423ee680c9327c589eca1d96da1e821097cfec Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 01:29:39 -0500 Subject: [PATCH 04/16] feat: implement TwitchHelix#deleteChatMessages --- .../github/twitch4j/helix/TwitchHelix.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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 3e1bbcbf3..d63d62b68 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 @@ -14,6 +14,7 @@ import feign.*; import org.apache.commons.io.IOUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.InputStream; @@ -1477,6 +1478,39 @@ HystrixCommand removeBlockedTerm( @Param("id") String blockedTermId ); + /** + * Removes a single chat message or all chat messages from the broadcaster’s chat room. + *

+ * This endpoint is in open beta. + *

+ * The ID in the moderator_id query parameter must match the user ID in the access token. + * If the broadcaster wants to remove messages themselves (instead of having the moderator do it), set this parameter to the broadcaster’s ID, too. + *

+ * The id tag in the PRIVMSG contains the message’s ID (see {@code IRCMessageEvent#getMessageId}). + * Restrictions: + *

    + *
  • The message must have been created within the last 6 hours.
  • + *
  • The message must not belong to the broadcaster.
  • + *
+ * If id is not specified, the request removes all messages in the broadcaster’s chat room. + * + * @param authToken User access token (scope: moderator:manage:chat_messages) of the broadcaster or a moderator. + * @param broadcasterId The ID of the broadcaster that owns the chat room to remove messages from. + * @param moderatorId The ID of a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the OAuth token. + * @param messageId The ID of the message to remove. If not specified, the request removes all messages in the broadcaster’s chat room. + * @return 204 No Content upon a successful call + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_CHAT_MESSAGES_MANAGE + */ + @Unofficial // beta + @RequestLine("DELETE /moderation/chat?broadcaster_id={broadcaster_id}&moderator_id={moderator_id}&message_id={message_id}") + @Headers("Authorization: Bearer {token}") + HystrixCommand deleteChatMessages( + @Param("token") String authToken, + @NotNull @Param("broadcaster_id") String broadcasterId, + @NotNull @Param("moderator_id") String moderatorId, + @Nullable @Param("message_id") String messageId + ); + /** * Determines whether a string message meets the channel’s AutoMod requirements. * From df149d0ae0e48d029a2d0d2e29abd546917a73df Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 12:42:25 -0500 Subject: [PATCH 05/16] feat: implement TwitchHelix#updateUserChatColor --- .../github/twitch4j/helix/TwitchHelix.java | 23 +++++++++++++ .../helix/domain/NamedUserChatColor.java | 34 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 rest-helix/src/main/java/com/github/twitch4j/helix/domain/NamedUserChatColor.java 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 d63d62b68..26d1d3b42 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 @@ -379,6 +379,29 @@ HystrixCommand getGlobalChatBadges( @Param("token") String authToken ); + /** + * Updates the color used for the user’s name in chat. + *

+ * This endpoint is in open beta. + *

+ * All users may specify one of the following named color values in {@link NamedUserChatColor}. + * Turbo and Prime users may specify a named color or a Hex color code like #9146FF. + * + * @param authToken User access token that includes the user:manage:chat_color scope. + * @param userId The ID of the user whose chat color you want to update. This must match the user ID in the access token. + * @param color The color to use for the user’s name in chat. + * @return 204 No Content upon a successful call + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_USER_COLOR_MANAGE + */ + @Unofficial // beta + @RequestLine("PUT /chat/color?user_id={user_id}&color={color}") + @Headers("Authorization: Bearer {token}") + HystrixCommand updateUserChatColor( + @Param("token") String authToken, + @Param("user_id") String userId, + @Param("color") String color + ); + /** * Gets all custom emotes for a specific Twitch channel including subscriber emotes, Bits tier emotes, and follower emotes. *

diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/NamedUserChatColor.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/NamedUserChatColor.java new file mode 100644 index 000000000..3e67d1973 --- /dev/null +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/NamedUserChatColor.java @@ -0,0 +1,34 @@ +package com.github.twitch4j.helix.domain; + +/** + * The color to use for the user’s name in chat. + *

+ * All users may specify one of the following named color values. + *

+ * Turbo and Prime users may specify a named color or a Hex color code like #9146FF. + * If you use a Hex color code, remember to URL encode it. + * + * @see com.github.twitch4j.helix.TwitchHelix#updateUserChatColor(String, String, String) + */ +public enum NamedUserChatColor { + BLUE, + BLUE_VIOLET, + CADET_BLUE, + CHOCOLATE, + CORAL, + DODGER_BLUE, + FIREBRICK, + GOLDEN_ROD, + GREEN, + HOT_PINK, + ORANGE_RED, + RED, + SEA_GREEN, + SPRING_GREEN, + YELLOW_GREEN; + + @Override + public String toString() { + return this.name().toLowerCase(); + } +} From 1df62625e532341b00a0697901b4bdbc3164ea4a Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 13:16:21 -0500 Subject: [PATCH 06/16] feat: implement TwitchHelix#addChannelModerator --- .../github/twitch4j/helix/TwitchHelix.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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 26d1d3b42..9ab2becd8 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 @@ -1573,6 +1573,25 @@ HystrixCommand getModerators( @Param("first") Integer limit ); + /** + * Adds a moderator to the broadcaster’s chat room. + *

+ * This endpoint is in open beta. + * + * @param authToken Broadcaster's user access token that includes the channel:manage:moderators scope. + * @param broadcasterId The ID of the broadcaster that owns the chat room. + * @param userId The ID of the user to add as a moderator in the broadcaster’s chat room. + * @return 204 No Content upon a successful call + */ + @Unofficial // beta + @RequestLine("POST /moderation/moderators?broadcaster_id={broadcaster_id}&user_id={user_id}") + @Headers("Authorization: Bearer {token}") + HystrixCommand addChannelModerator( + @Param("token") String authToken, + @Param("broadcaster_id") String broadcasterId, + @Param("user_id") String userId + ); + /** * Returns all moderators in a channel. * From 4dc0311885b752ddfd4d43e44a3a33e63f855cad Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 13:19:35 -0500 Subject: [PATCH 07/16] feat: implement TwitchHelix#addChannelModerator --- .../github/twitch4j/helix/TwitchHelix.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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 9ab2becd8..8fd211b52 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 @@ -1582,6 +1582,7 @@ HystrixCommand getModerators( * @param broadcasterId The ID of the broadcaster that owns the chat room. * @param userId The ID of the user to add as a moderator in the broadcaster’s chat room. * @return 204 No Content upon a successful call + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_CHANNEL_MODS_MANAGE */ @Unofficial // beta @RequestLine("POST /moderation/moderators?broadcaster_id={broadcaster_id}&user_id={user_id}") @@ -1592,6 +1593,26 @@ HystrixCommand addChannelModerator( @Param("user_id") String userId ); + /** + * Removes a moderator from the broadcaster’s chat room. + *

+ * This endpoint is in open beta. + * + * @param authToken Broadcaster's user access token that includes the channel:manage:moderators scope. + * @param broadcasterId The ID of the broadcaster that owns the chat room. + * @param userId The ID of the user to remove as a moderator from the broadcaster’s chat room. + * @return 204 No Content upon a successful call + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_CHANNEL_MODS_MANAGE + */ + @Unofficial // beta + @RequestLine("DELETE /moderation/moderators?broadcaster_id={broadcaster_id}&user_id={user_id}") + @Headers("Authorization: Bearer {token}") + HystrixCommand removeChannelModerator( + @Param("token") String authToken, + @Param("broadcaster_id") String broadcasterId, + @Param("user_id") String userId + ); + /** * Returns all moderators in a channel. * From 6373165cc6a8cc0e95bbf854bd98f05b2fb95d23 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 13:25:14 -0500 Subject: [PATCH 08/16] feat: implement TwitchHelix#addChannelVip --- .../github/twitch4j/helix/TwitchHelix.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 8fd211b52..b56cc98dc 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 @@ -1086,6 +1086,26 @@ HystrixCommand getChannelEditors( @Param("broadcaster_id") String broadcasterId ); + /** + * Adds a VIP to the broadcaster’s chat room. + *

+ * This endpoint is in open beta. + * + * @param authToken Broadcaster's user access token that includes the channel:manage:vips scope. + * @param broadcasterId The ID of the broadcaster that’s granting VIP status to the user. + * @param userId The ID of the user to add as a VIP in the broadcaster’s chat room. + * @return 204 No Content upon a successful call + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_CHANNEL_VIPS_MANAGE + */ + @Unofficial // beta + @RequestLine("POST /channels/vips?broadcaster_id={broadcaster_id}&user_id={user_id}") + @Headers("Authorization: Bearer {token}") + HystrixCommand addChannelVip( + @Param("token") String authToken, + @Param("broadcaster_id") String broadcasterId, + @Param("user_id") String userId + ); + /** * Creates a clip programmatically. This returns both an ID and an edit URL for the new clip. * From 71425c1024e797d4b08f7ea364886dd20c6b4697 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 13:30:25 -0500 Subject: [PATCH 09/16] feat: implement TwitchHelix#removeChannelVip --- .../github/twitch4j/helix/TwitchHelix.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 b56cc98dc..91d0e6d0e 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 @@ -1106,6 +1106,26 @@ HystrixCommand addChannelVip( @Param("user_id") String userId ); + /** + * Removes a VIP from the broadcaster’s chat room. + *

+ * This endpoint is in open beta. + * + * @param authToken Broadcaster's user access token that includes the channel:manage:vips scope. + * @param broadcasterId The ID of the broadcaster that’s removing VIP status from the user. + * @param userId The ID of the user to remove as a VIP from the broadcaster’s chat room. + * @return 204 No Content upon a successful call + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_CHANNEL_VIPS_MANAGE + */ + @Unofficial // beta + @RequestLine("DELETE /channels/vips?broadcaster_id={broadcaster_id}&user_id={user_id}") + @Headers("Authorization: Bearer {token}") + HystrixCommand removeChannelVip( + @Param("token") String authToken, + @Param("broadcaster_id") String broadcasterId, + @Param("user_id") String userId + ); + /** * Creates a clip programmatically. This returns both an ID and an edit URL for the new clip. * From 89913c5407b8e2c10e40995bbfffce8af8256f94 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 13:41:41 -0500 Subject: [PATCH 10/16] feat: implement TwitchHelix#getChannelVips --- .../github/twitch4j/helix/TwitchHelix.java | 24 +++++++++++++++++ .../twitch4j/helix/domain/ChannelVip.java | 26 +++++++++++++++++++ .../twitch4j/helix/domain/ChannelVipList.java | 26 +++++++++++++++++++ .../helix/domain/HelixPagination.java | 5 ++++ 4 files changed, 81 insertions(+) create mode 100644 rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChannelVip.java create mode 100644 rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChannelVipList.java 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 91d0e6d0e..e3ac81270 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 @@ -1086,6 +1086,30 @@ HystrixCommand getChannelEditors( @Param("broadcaster_id") String broadcasterId ); + /** + * Gets a list of the channel’s VIPs. + *

+ * This endpoint is in open beta. + * + * @param authToken Broadcaster's user access token that includes the channel:read:vips scope. + * @param broadcasterId The ID of the broadcaster whose list of VIPs you want to get. + * @param userIds Filters the list for specific VIPs. The maximum number of IDs that you may specify is 100. + * @param limit The maximum number of items to return per page in the response. Minimum: 1. Maximum: 100. Default: 20. + * @param after The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value. + * @return ChannelVipList + * @see com.github.twitch4j.auth.domain.TwitchScopes#HELIX_CHANNEL_VIPS_READ + */ + @Unofficial // beta + @RequestLine("GET /channels/vips?broadcaster_id={broadcaster_id}&user_id={user_id}&first={first}&after={after}") + @Headers("Authorization: Bearer {token}") + HystrixCommand getChannelVips( + @Param("token") String authToken, + @NotNull @Param("broadcaster_id") String broadcasterId, + @Nullable @Param("user_id") List userIds, + @Nullable @Param("first") Integer limit, + @Nullable @Param("after") String after + ); + /** * Adds a VIP to the broadcaster’s chat room. *

diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChannelVip.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChannelVip.java new file mode 100644 index 000000000..75659a5ac --- /dev/null +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChannelVip.java @@ -0,0 +1,26 @@ +package com.github.twitch4j.helix.domain; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; + +@Data +@Setter(AccessLevel.PRIVATE) +public class ChannelVip { + + /** + * An ID that uniquely identifies the VIP user. + */ + private String userId; + + /** + * The user’s display name. + */ + private String userName; + + /** + * The user’s login name. + */ + private String userLogin; + +} diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChannelVipList.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChannelVipList.java new file mode 100644 index 000000000..9c9ab6007 --- /dev/null +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChannelVipList.java @@ -0,0 +1,26 @@ +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 ChannelVipList { + + /** + * The list of VIPs. + *

+ * The list is empty if the channel doesn't have VIP users. + * The list does not include the broadcaster. + */ + private List data; + + /** + * Contains the information used to page through the list of results. + */ + private HelixPagination pagination; + +} diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/HelixPagination.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/HelixPagination.java index 19b15df96..96a8f988c 100644 --- a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/HelixPagination.java +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/HelixPagination.java @@ -4,6 +4,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.Setter; +import org.jetbrains.annotations.Nullable; /** * Pagination @@ -13,6 +14,10 @@ @NoArgsConstructor public class HelixPagination { + /** + * The cursor used to get the next page of results. Use the cursor to set the request’s after query parameter. + */ + @Nullable private String cursor; } From 81835c464f34820721385099d320327e11fc5144 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 16:07:43 -0500 Subject: [PATCH 11/16] feat: implement TwitchHelix#getUserChatColor --- .../github/twitch4j/helix/TwitchHelix.java | 18 ++++++++++ .../twitch4j/helix/domain/ChatUserColor.java | 33 +++++++++++++++++++ .../helix/domain/UserChatColorList.java | 18 ++++++++++ 3 files changed, 69 insertions(+) create mode 100644 rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChatUserColor.java create mode 100644 rest-helix/src/main/java/com/github/twitch4j/helix/domain/UserChatColorList.java 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 e3ac81270..910fe10f4 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 @@ -379,6 +379,24 @@ HystrixCommand getGlobalChatBadges( @Param("token") String authToken ); + /** + * Gets the color used for the user’s name in chat. + *

+ * This endpoint is in open beta. + * + * @param authToken App access token or user access token. + * @param userIds The ID of the users whose color you want to get. Maximum: 100. + * @return UserChatColorList + * @see ChatUserColor#getColor() + */ + @Unofficial // beta + @RequestLine("GET /chat/color?user_id={user_id}") + @Headers("Authorization: Bearer {token}") + HystrixCommand getUserChatColor( + @Param("token") String authToken, + @Param("user_id") List userIds + ); + /** * Updates the color used for the user’s name in chat. *

diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChatUserColor.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChatUserColor.java new file mode 100644 index 000000000..09ac93ae5 --- /dev/null +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/ChatUserColor.java @@ -0,0 +1,33 @@ +package com.github.twitch4j.helix.domain; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; + +@Data +@Setter(AccessLevel.PRIVATE) +public class ChatUserColor { + + /** + * The ID of the user. + */ + private String userId; + + /** + * The user’s display name. + */ + private String userName; + + /** + * The user’s login name. + */ + private String userLogin; + + /** + * The Hex color code that the user uses in chat for their name. + *

+ * If the user hasn't specified a color in their settings, the string is empty. + */ + private String color; + +} diff --git a/rest-helix/src/main/java/com/github/twitch4j/helix/domain/UserChatColorList.java b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/UserChatColorList.java new file mode 100644 index 000000000..25f803182 --- /dev/null +++ b/rest-helix/src/main/java/com/github/twitch4j/helix/domain/UserChatColorList.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 UserChatColorList { + + /** + * The list of users and the color code that’s used for their name. + */ + private List data; + +} From 138842fb951ea36cd90de01cbda851d653698124 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 16:23:15 -0500 Subject: [PATCH 12/16] feat: rate limit TwitchHelix#sendWhisper --- .../helix/interceptor/TwitchHelixHttpClient.java | 10 ++++++++++ .../interceptor/TwitchHelixRateLimitTracker.java | 15 +++++++++++++++ 2 files changed, 25 insertions(+) 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 a168ac01b..f051fea10 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 @@ -120,6 +120,16 @@ private Response delegatedExecute(Request request, Request.Options options) thro return executeAgainstBucket(raidBucket, () -> client.execute(request, options)); } + // Whispers API: sendWhisper has a stricter bucket that applies per user id + if (templatePath.endsWith("/whispers")) { + // Obtain the user id + String userId = request.requestTemplate().queries().getOrDefault("from_user_id", Collections.emptyList()).iterator().next(); + + // Conform to endpoint-specific bucket + Bucket whisperBucket = rateLimitTracker.getWhispersBucket(userId); + return executeAgainstBucket(whisperBucket, () -> 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 434cddc8c..fa04f4c67 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 @@ -25,6 +25,8 @@ public final class TwitchHelixRateLimitTracker { private static final String AUTOMOD_STATUS_MINUTE_ID = TwitchLimitType.HELIX_AUTOMOD_STATUS_LIMIT + "-min"; private static final String AUTOMOD_STATUS_HOUR_ID = TwitchLimitType.HELIX_AUTOMOD_STATUS_LIMIT + "-hr"; + private static final String WHISPER_MINUTE_BANDWIDTH_ID = TwitchLimitType.CHAT_WHISPER_LIMIT.getBandwidthId() + "-minute"; + private static final String WHISPER_SECOND_BANDWIDTH_ID = TwitchLimitType.CHAT_WHISPER_LIMIT.getBandwidthId() + "-second"; /** * @see TwitchLimitType#HELIX_AUTOMOD_STATUS_LIMIT @@ -50,6 +52,14 @@ public final class TwitchHelixRateLimitTracker { Bandwidth.simple(300, Duration.ofHours(1L)).withId(AUTOMOD_STATUS_HOUR_ID) ); + /** + * @see TwitchLimitType#CHAT_WHISPER_LIMIT + */ + private static final List USER_WHISPER_BANDWIDTH = Arrays.asList( + Bandwidth.simple(100, Duration.ofSeconds(60)).withId(WHISPER_MINUTE_BANDWIDTH_ID), + Bandwidth.simple(3, Duration.ofSeconds(1)).withId(WHISPER_SECOND_BANDWIDTH_ID) + ); + /** * 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)} */ @@ -151,6 +161,11 @@ Bucket getRaidsBucket(@NotNull String channelId) { return raidsByChannelId.get(channelId, k -> BucketUtils.createBucket(RAIDS_BANDWIDTH)); } + @NotNull + Bucket getWhispersBucket(@NotNull String userId) { + return TwitchLimitRegistry.getInstance().getOrInitializeBucket(userId, TwitchLimitType.CHAT_WHISPER_LIMIT, USER_WHISPER_BANDWIDTH); + } + @NotNull @Unofficial Bucket getModerationBucket(@NotNull String channelId) { From 2346b9983863496d70e0e010b00d07495cc393a2 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 16:35:24 -0500 Subject: [PATCH 13/16] feat: rate limit add & remove mod endpoints --- .../interceptor/TwitchHelixHttpClient.java | 10 ++++++++ .../TwitchHelixRateLimitTracker.java | 23 +++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) 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 f051fea10..0037619a7 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 @@ -97,6 +97,16 @@ private Response delegatedExecute(Request request, Request.Options options) thro return executeAgainstBucket(termsBucket, () -> client.execute(request, options)); } + // Moderation API: addChannelModerator and removeChannelModerator (likely) share a bucket per channel id + if (templatePath.endsWith("/moderation/moderators") && (request.httpMethod() == Request.HttpMethod.POST || request.httpMethod() == Request.HttpMethod.DELETE)) { + // Obtain the channel id + String channelId = request.requestTemplate().queries().getOrDefault("broadcaster_id", Collections.emptyList()).iterator().next(); + + // Conform to endpoint-specific bucket + Bucket modsBucket = rateLimitTracker.getModeratorsBucket(channelId); + return executeAgainstBucket(modsBucket, () -> client.execute(request, options)); + } + // Clips API: createClip has a stricter bucket that applies per user id if (request.httpMethod() == Request.HttpMethod.POST && templatePath.endsWith("/clips")) { // Obtain user id 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 fa04f4c67..1d0221572 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 @@ -53,13 +53,20 @@ public final class TwitchHelixRateLimitTracker { ); /** + * Officially documented rate limit for {@link com.github.twitch4j.helix.TwitchHelix#sendWhisper(String, String, String, String)} + * * @see TwitchLimitType#CHAT_WHISPER_LIMIT */ - private static final List USER_WHISPER_BANDWIDTH = Arrays.asList( + private static final List WHISPERS_BANDWIDTH = Arrays.asList( Bandwidth.simple(100, Duration.ofSeconds(60)).withId(WHISPER_MINUTE_BANDWIDTH_ID), Bandwidth.simple(3, Duration.ofSeconds(1)).withId(WHISPER_SECOND_BANDWIDTH_ID) ); + /** + * Officially documented rate limit for {@link com.github.twitch4j.helix.TwitchHelix#addChannelModerator(String, String, String)} and {@link com.github.twitch4j.helix.TwitchHelix#removeChannelModerator(String, String, String)} + */ + private static final Bandwidth MOD_BANDWIDTH = Bandwidth.simple(10, Duration.ofSeconds(10)); + /** * 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)} */ @@ -90,6 +97,13 @@ public final class TwitchHelixRateLimitTracker { .expireAfterAccess(1, TimeUnit.MINUTES) .build(); + /** + * Moderators API: add and remove moderator rate limit buckets per channel + */ + private final Cache moderatorsByChannelId = Caffeine.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(); + /** * Raids API: start and cancel raid rate limit buckets per channel */ @@ -156,6 +170,11 @@ Bucket getAutomodStatusBucket(@NotNull String channelId) { return TwitchLimitRegistry.getInstance().getOrInitializeBucket(channelId, TwitchLimitType.HELIX_AUTOMOD_STATUS_LIMIT, AUTOMOD_STATUS_NORMAL_BANDWIDTH); } + @NotNull + Bucket getModeratorsBucket(@NotNull String channelId) { + return moderatorsByChannelId.get(channelId, k -> BucketUtils.createBucket(MOD_BANDWIDTH)); + } + @NotNull Bucket getRaidsBucket(@NotNull String channelId) { return raidsByChannelId.get(channelId, k -> BucketUtils.createBucket(RAIDS_BANDWIDTH)); @@ -163,7 +182,7 @@ Bucket getRaidsBucket(@NotNull String channelId) { @NotNull Bucket getWhispersBucket(@NotNull String userId) { - return TwitchLimitRegistry.getInstance().getOrInitializeBucket(userId, TwitchLimitType.CHAT_WHISPER_LIMIT, USER_WHISPER_BANDWIDTH); + return TwitchLimitRegistry.getInstance().getOrInitializeBucket(userId, TwitchLimitType.CHAT_WHISPER_LIMIT, WHISPERS_BANDWIDTH); } @NotNull From 318769c630fdd54247e646958f9cb374d54faa73 Mon Sep 17 00:00:00 2001 From: Sidd Date: Sat, 16 Jul 2022 16:41:15 -0500 Subject: [PATCH 14/16] feat: rate limit add & remove vip endpoints --- .../interceptor/TwitchHelixHttpClient.java | 10 +++++ .../TwitchHelixRateLimitTracker.java | 37 ++++++++++++++----- 2 files changed, 37 insertions(+), 10 deletions(-) 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 0037619a7..fba332da6 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 @@ -67,6 +67,16 @@ public Response execute(Request request, Request.Options options) throws IOExcep private Response delegatedExecute(Request request, Request.Options options) throws IOException { String templatePath = request.requestTemplate().path(); + // Channels API: addChannelVip and removeChannelVip (likely) share a bucket per channel id + if (templatePath.endsWith("/channels/vips") && (request.httpMethod() == Request.HttpMethod.POST || request.httpMethod() == Request.HttpMethod.DELETE)) { + // Obtain the channel id + String channelId = request.requestTemplate().queries().getOrDefault("broadcaster_id", Collections.emptyList()).iterator().next(); + + // Conform to endpoint-specific bucket + Bucket vipBucket = rateLimitTracker.getVipsBucket(channelId); + return executeAgainstBucket(vipBucket, () -> client.execute(request, options)); + } + // Moderation API: Check AutoMod Status has a stricter bucket that applies per channel id if (request.httpMethod() == Request.HttpMethod.POST && templatePath.endsWith("/moderation/enforcements/status")) { // Obtain the channel id 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 1d0221572..090fb39b1 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 @@ -52,16 +52,6 @@ public final class TwitchHelixRateLimitTracker { Bandwidth.simple(300, Duration.ofHours(1L)).withId(AUTOMOD_STATUS_HOUR_ID) ); - /** - * Officially documented rate limit for {@link com.github.twitch4j.helix.TwitchHelix#sendWhisper(String, String, String, String)} - * - * @see TwitchLimitType#CHAT_WHISPER_LIMIT - */ - private static final List WHISPERS_BANDWIDTH = Arrays.asList( - Bandwidth.simple(100, Duration.ofSeconds(60)).withId(WHISPER_MINUTE_BANDWIDTH_ID), - Bandwidth.simple(3, Duration.ofSeconds(1)).withId(WHISPER_SECOND_BANDWIDTH_ID) - ); - /** * Officially documented rate limit for {@link com.github.twitch4j.helix.TwitchHelix#addChannelModerator(String, String, String)} and {@link com.github.twitch4j.helix.TwitchHelix#removeChannelModerator(String, String, String)} */ @@ -72,6 +62,21 @@ public final class TwitchHelixRateLimitTracker { */ private static final Bandwidth RAIDS_BANDWIDTH = Bandwidth.simple(10, Duration.ofMinutes(10)); + /** + * Officially documented rate limit for {@link com.github.twitch4j.helix.TwitchHelix#addChannelVip(String, String, String)} and {@link com.github.twitch4j.helix.TwitchHelix#removeChannelVip(String, String, String)} + */ + private static final Bandwidth VIP_BANDWIDTH = Bandwidth.simple(10, Duration.ofSeconds(10)); + + /** + * Officially documented rate limit for {@link com.github.twitch4j.helix.TwitchHelix#sendWhisper(String, String, String, String)} + * + * @see TwitchLimitType#CHAT_WHISPER_LIMIT + */ + private static final List WHISPERS_BANDWIDTH = Arrays.asList( + Bandwidth.simple(100, Duration.ofSeconds(60)).withId(WHISPER_MINUTE_BANDWIDTH_ID), + Bandwidth.simple(3, Duration.ofSeconds(1)).withId(WHISPER_SECOND_BANDWIDTH_ID) + ); + /** * Empirically determined rate limit on helix bans and unbans, per channel */ @@ -111,6 +116,13 @@ public final class TwitchHelixRateLimitTracker { .expireAfterAccess(10, TimeUnit.MINUTES) .build(); + /** + * Channels API: add and remove VIP rate limit buckets per channel + */ + private final Cache vipsByChannelId = Caffeine.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(); + /** * Moderation API: ban and unban rate limit buckets per channel */ @@ -180,6 +192,11 @@ Bucket getRaidsBucket(@NotNull String channelId) { return raidsByChannelId.get(channelId, k -> BucketUtils.createBucket(RAIDS_BANDWIDTH)); } + @NotNull + Bucket getVipsBucket(@NotNull String channelId) { + return vipsByChannelId.get(channelId, k -> BucketUtils.createBucket(VIP_BANDWIDTH)); + } + @NotNull Bucket getWhispersBucket(@NotNull String userId) { return TwitchLimitRegistry.getInstance().getOrInitializeBucket(userId, TwitchLimitType.CHAT_WHISPER_LIMIT, WHISPERS_BANDWIDTH); From 17e7d6a77e63233b01125a0173716edf173931fe Mon Sep 17 00:00:00 2001 From: Sidd Date: Tue, 19 Jul 2022 00:19:28 -0500 Subject: [PATCH 15/16] fix: separate add/remove mod/vip buckets --- .../interceptor/TwitchHelixHttpClient.java | 32 +++++++++++---- .../TwitchHelixRateLimitTracker.java | 40 +++++++++++++++---- 2 files changed, 57 insertions(+), 15 deletions(-) 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 fba332da6..71560b3b1 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 @@ -68,13 +68,22 @@ private Response delegatedExecute(Request request, Request.Options options) thro String templatePath = request.requestTemplate().path(); // Channels API: addChannelVip and removeChannelVip (likely) share a bucket per channel id - if (templatePath.endsWith("/channels/vips") && (request.httpMethod() == Request.HttpMethod.POST || request.httpMethod() == Request.HttpMethod.DELETE)) { + if (templatePath.endsWith("/channels/vips")) { // Obtain the channel id String channelId = request.requestTemplate().queries().getOrDefault("broadcaster_id", Collections.emptyList()).iterator().next(); // Conform to endpoint-specific bucket - Bucket vipBucket = rateLimitTracker.getVipsBucket(channelId); - return executeAgainstBucket(vipBucket, () -> client.execute(request, options)); + Bucket vipBucket; + if (request.httpMethod() == Request.HttpMethod.POST) { + vipBucket = rateLimitTracker.getVipAddBucket(channelId); + } else if (request.httpMethod() == Request.HttpMethod.DELETE) { + vipBucket = rateLimitTracker.getVipRemoveBucket(channelId); + } else { + vipBucket = null; + } + + if (vipBucket != null) + return executeAgainstBucket(vipBucket, () -> client.execute(request, options)); } // Moderation API: Check AutoMod Status has a stricter bucket that applies per channel id @@ -107,14 +116,23 @@ private Response delegatedExecute(Request request, Request.Options options) thro return executeAgainstBucket(termsBucket, () -> client.execute(request, options)); } - // Moderation API: addChannelModerator and removeChannelModerator (likely) share a bucket per channel id - if (templatePath.endsWith("/moderation/moderators") && (request.httpMethod() == Request.HttpMethod.POST || request.httpMethod() == Request.HttpMethod.DELETE)) { + // Moderation API: addChannelModerator and removeChannelModerator have independent buckets per channel id + if (templatePath.endsWith("/moderation/moderators")) { // Obtain the channel id String channelId = request.requestTemplate().queries().getOrDefault("broadcaster_id", Collections.emptyList()).iterator().next(); // Conform to endpoint-specific bucket - Bucket modsBucket = rateLimitTracker.getModeratorsBucket(channelId); - return executeAgainstBucket(modsBucket, () -> client.execute(request, options)); + Bucket modsBucket; + if (request.httpMethod() == Request.HttpMethod.POST) { + modsBucket = rateLimitTracker.getModAddBucket(channelId); + } else if (request.httpMethod() == Request.HttpMethod.DELETE) { + modsBucket = rateLimitTracker.getModRemoveBucket(channelId); + } else { + modsBucket = null; + } + + if (modsBucket != null) + return executeAgainstBucket(modsBucket, () -> client.execute(request, options)); } // Clips API: createClip has a stricter bucket that applies per user id 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 090fb39b1..2b8f2af8f 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 @@ -103,9 +103,16 @@ public final class TwitchHelixRateLimitTracker { .build(); /** - * Moderators API: add and remove moderator rate limit buckets per channel + * Moderators API: add moderator rate limit bucket per channel */ - private final Cache moderatorsByChannelId = Caffeine.newBuilder() + private final Cache addModByChannelId = Caffeine.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(); + + /** + * Moderators API: remove moderator rate limit bucket per channel + */ + private final Cache removeModByChannelId = Caffeine.newBuilder() .expireAfterAccess(30, TimeUnit.SECONDS) .build(); @@ -117,9 +124,16 @@ public final class TwitchHelixRateLimitTracker { .build(); /** - * Channels API: add and remove VIP rate limit buckets per channel + * Channels API: add VIP rate limit bucket per channel + */ + private final Cache addVipByChannelId = Caffeine.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(); + + /** + * Channels API: remove VIP rate limit bucket per channel */ - private final Cache vipsByChannelId = Caffeine.newBuilder() + private final Cache removeVipByChannelId = Caffeine.newBuilder() .expireAfterAccess(30, TimeUnit.SECONDS) .build(); @@ -183,8 +197,13 @@ Bucket getAutomodStatusBucket(@NotNull String channelId) { } @NotNull - Bucket getModeratorsBucket(@NotNull String channelId) { - return moderatorsByChannelId.get(channelId, k -> BucketUtils.createBucket(MOD_BANDWIDTH)); + Bucket getModAddBucket(@NotNull String channelId) { + return addModByChannelId.get(channelId, k -> BucketUtils.createBucket(MOD_BANDWIDTH)); + } + + @NotNull + Bucket getModRemoveBucket(@NotNull String channelId) { + return removeModByChannelId.get(channelId, k -> BucketUtils.createBucket(MOD_BANDWIDTH)); } @NotNull @@ -193,8 +212,13 @@ Bucket getRaidsBucket(@NotNull String channelId) { } @NotNull - Bucket getVipsBucket(@NotNull String channelId) { - return vipsByChannelId.get(channelId, k -> BucketUtils.createBucket(VIP_BANDWIDTH)); + Bucket getVipAddBucket(@NotNull String channelId) { + return addVipByChannelId.get(channelId, k -> BucketUtils.createBucket(VIP_BANDWIDTH)); + } + + @NotNull + Bucket getVipRemoveBucket(@NotNull String channelId) { + return removeVipByChannelId.get(channelId, k -> BucketUtils.createBucket(VIP_BANDWIDTH)); } @NotNull From 2f770fb80cef41d67cfc1768e9f358311a9ccb5c Mon Sep 17 00:00:00 2001 From: Philipp Heuer Date: Tue, 19 Jul 2022 20:25:12 +0200 Subject: [PATCH 16/16] feat: add nullable annotations on added methods --- .../github/twitch4j/helix/TwitchHelix.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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 910fe10f4..b301d76c5 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 @@ -394,7 +394,7 @@ HystrixCommand getGlobalChatBadges( @Headers("Authorization: Bearer {token}") HystrixCommand getUserChatColor( @Param("token") String authToken, - @Param("user_id") List userIds + @NotNull @Param("user_id") List userIds ); /** @@ -416,8 +416,8 @@ HystrixCommand getUserChatColor( @Headers("Authorization: Bearer {token}") HystrixCommand updateUserChatColor( @Param("token") String authToken, - @Param("user_id") String userId, - @Param("color") String color + @NotNull @Param("user_id") String userId, + @NotNull @Param("color") String color ); /** @@ -1144,8 +1144,8 @@ HystrixCommand getChannelVips( @Headers("Authorization: Bearer {token}") HystrixCommand addChannelVip( @Param("token") String authToken, - @Param("broadcaster_id") String broadcasterId, - @Param("user_id") String userId + @NotNull @Param("broadcaster_id") String broadcasterId, + @NotNull @Param("user_id") String userId ); /** @@ -1164,8 +1164,8 @@ HystrixCommand addChannelVip( @Headers("Authorization: Bearer {token}") HystrixCommand removeChannelVip( @Param("token") String authToken, - @Param("broadcaster_id") String broadcasterId, - @Param("user_id") String userId + @NotNull @Param("broadcaster_id") String broadcasterId, + @NotNull @Param("user_id") String userId ); /** @@ -1671,8 +1671,8 @@ HystrixCommand getModerators( @Headers("Authorization: Bearer {token}") HystrixCommand addChannelModerator( @Param("token") String authToken, - @Param("broadcaster_id") String broadcasterId, - @Param("user_id") String userId + @NotNull @Param("broadcaster_id") String broadcasterId, + @NotNull @Param("user_id") String userId ); /** @@ -1691,8 +1691,8 @@ HystrixCommand addChannelModerator( @Headers("Authorization: Bearer {token}") HystrixCommand removeChannelModerator( @Param("token") String authToken, - @Param("broadcaster_id") String broadcasterId, - @Param("user_id") String userId + @NotNull @Param("broadcaster_id") String broadcasterId, + @NotNull @Param("user_id") String userId ); /**