Skip to content

Commit

Permalink
feat: implement new beta helix chat endpoints (#606)
Browse files Browse the repository at this point in the history
* chore: add new scopes

* feat: implement TwitchHelix#sendWhisper

* feat: implement TwitchHelix#sendChatAnnouncement

* feat: implement TwitchHelix#deleteChatMessages

* feat: implement TwitchHelix#updateUserChatColor

* feat: implement TwitchHelix#addChannelModerator

* feat: implement TwitchHelix#addChannelModerator

* feat: implement TwitchHelix#addChannelVip

* feat: implement TwitchHelix#removeChannelVip

* feat: implement TwitchHelix#getChannelVips

* feat: implement TwitchHelix#getUserChatColor

* feat: rate limit TwitchHelix#sendWhisper

* feat: rate limit add & remove mod endpoints

* feat: rate limit add & remove vip endpoints

* fix: separate add/remove mod/vip buckets

* feat: add nullable annotations on added methods

Co-authored-by: Philipp Heuer <git@philippheuer.me>
  • Loading branch information
iProdigy and PhilippHeuer committed Jul 19, 2022
1 parent d119698 commit 2405187
Show file tree
Hide file tree
Showing 11 changed files with 543 additions and 0 deletions.
Expand Up @@ -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"),
Expand All @@ -29,15 +30,20 @@ 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"),
HELIX_AUTOMOD_SETTINGS_READ("moderator:read:automod_settings"),
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"),
Expand All @@ -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"),
Expand Down
254 changes: 254 additions & 0 deletions rest-helix/src/main/java/com/github/twitch4j/helix/TwitchHelix.java

Large diffs are not rendered by default.

@@ -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();
}
}
@@ -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;

}
@@ -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.
* <p>
* The list is empty if the channel doesn't have VIP users.
* The list does not include the broadcaster.
*/
private List<ChannelVip> data;

/**
* Contains the information used to page through the list of results.
*/
private HelixPagination pagination;

}
@@ -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.
* <p>
* If the user hasn't specified a color in their settings, the string is empty.
*/
private String color;

}
Expand Up @@ -4,6 +4,7 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.Nullable;

/**
* Pagination
Expand All @@ -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;

}
@@ -0,0 +1,34 @@
package com.github.twitch4j.helix.domain;

/**
* The color to use for the user’s name in chat.
* <p>
* All users may specify one of the following named color values.
* <p>
* 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();
}
}
@@ -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<ChatUserColor> data;

}
Expand Up @@ -67,6 +67,25 @@ 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")) {
// Obtain the channel id
String channelId = request.requestTemplate().queries().getOrDefault("broadcaster_id", Collections.emptyList()).iterator().next();

// Conform to endpoint-specific bucket
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
if (request.httpMethod() == Request.HttpMethod.POST && templatePath.endsWith("/moderation/enforcements/status")) {
// Obtain the channel id
Expand Down Expand Up @@ -97,6 +116,25 @@ private Response delegatedExecute(Request request, Request.Options options) thro
return executeAgainstBucket(termsBucket, () -> client.execute(request, options));
}

// 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;
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
if (request.httpMethod() == Request.HttpMethod.POST && templatePath.endsWith("/clips")) {
// Obtain user id
Expand All @@ -120,6 +158,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);
}
Expand Down

0 comments on commit 2405187

Please sign in to comment.