Skip to content

Commit

Permalink
Add BetterTTV emotes
Browse files Browse the repository at this point in the history
  • Loading branch information
Vinrobot committed Nov 19, 2023
1 parent f5237fa commit f0f523b
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package net.vinrobot.mcemote.client.providers;

import net.minecraft.client.texture.NativeImage;
import net.vinrobot.mcemote.api.bttv.Emote;
import net.vinrobot.mcemote.client.helpers.NativeImageHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.Objects;

public class BTTVEmote implements net.vinrobot.mcemote.client.font.Emote {
private static final Logger LOGGER = LoggerFactory.getLogger(BTTVEmote.class);
private static final int DEFAULT_SIZE = 28;

private final Emote emote;

public BTTVEmote(Emote emote) {
this.emote = emote;
}

@Override
public String getName() {
return this.emote.code();
}

@Override
public int getWidth() {
final int width = this.emote.width();
return width > 0 ? width : DEFAULT_SIZE;
}

@Override
public int getHeight() {
final int height = this.emote.height();
return height > 0 ? height : DEFAULT_SIZE;
}

@Override
public Frame[] loadFrames() throws IOException {
if (this.emote.animated()) {
LOGGER.error("Animated BTTV emotes are not supported yet.");
}

final URL url = new URL("https://cdn.betterttv.net/emote/" + this.emote.id() + "/1x");
final BufferedImage image = Objects.requireNonNull(ImageIO.read(url));

final int expectedWidth = getWidth(), expectedHeight = getHeight();
final int actualWidth = image.getWidth(), actualHeight = image.getHeight();
if (expectedWidth != actualWidth || expectedHeight != actualHeight) {
final String expectedSize = expectedWidth + "x" + expectedHeight;
final String actualSize = actualWidth + "x" + actualHeight;
LOGGER.error("BTTV emote " + getName() + " has unexpected size " + actualSize + " (expected " + expectedSize + ").");
}

final NativeImage nativeImage = NativeImageHelper.fromBufferedImage(image);
return new Frame[]{new Frame(nativeImage)};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package net.vinrobot.mcemote.client.providers;

import net.vinrobot.mcemote.api.bttv.BetterTTVService;
import net.vinrobot.mcemote.api.bttv.Emote;
import net.vinrobot.mcemote.config.Configuration;

import java.util.Arrays;

public class BTTVGlobalEmoteProvider implements IEmoteProvider {
@Override
public int priority() {
return 10;
}

@Override
public void registerEmotes(final Configuration config, final IEmoteRegistry registry) throws Exception {
final BetterTTVService service = new BetterTTVService();

final Emote[] emoteSets = service.fetchGlobalEmoteSet();

Arrays.stream(emoteSets)
.map(BTTVEmote::new)
.forEach(registry::registerEmote);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package net.vinrobot.mcemote.client.providers;

import net.vinrobot.mcemote.api.bttv.BetterTTVService;
import net.vinrobot.mcemote.api.bttv.Provider;
import net.vinrobot.mcemote.api.bttv.UserResponse;
import net.vinrobot.mcemote.config.Configuration;

import java.util.Arrays;
import java.util.stream.Stream;

public class BTTVUserEmoteProvider implements IEmoteProvider {
@Override
public void registerEmotes(final Configuration config, final IEmoteRegistry registry) throws Exception {
final String twitchId = config.twitchId().get();
if (twitchId.isEmpty()) {
return;
}

final BetterTTVService service = new BetterTTVService();

final UserResponse emoteSets = service.fetchUserEmoteSet(Provider.TWITCH, twitchId);

Stream.concat(Arrays.stream(emoteSets.channelEmotes()), Arrays.stream(emoteSets.sharedEmotes()))
.map(BTTVEmote::new)
.forEach(registry::registerEmote);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ net.vinrobot.mcemote.client.providers.STVGlobalEmoteProvider
net.vinrobot.mcemote.client.providers.STVUserEmoteProvider
net.vinrobot.mcemote.client.providers.FFZGlobalEmoteProvider
net.vinrobot.mcemote.client.providers.FFZRoomEmoteProvider
net.vinrobot.mcemote.client.providers.BTTVGlobalEmoteProvider
net.vinrobot.mcemote.client.providers.BTTVUserEmoteProvider
45 changes: 45 additions & 0 deletions src/main/java/net/vinrobot/mcemote/api/bttv/BetterTTVService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package net.vinrobot.mcemote.api.bttv;

import com.google.gson.Gson;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class BetterTTVService {
public static final String BASE_API = "https://api.betterttv.net/3/cached";
public static final URI GLOBAL_EMOTE_SET_URI = URI.create(BASE_API + "/emotes/global");

private final HttpClient httpClient;
private final Gson gson = new Gson();

public BetterTTVService() {
this(HttpClient.newHttpClient());
}

public BetterTTVService(HttpClient httpClient) {
this.httpClient = httpClient;
}

public Emote[] fetchGlobalEmoteSet() throws IOException, InterruptedException {
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(GLOBAL_EMOTE_SET_URI)
.build();

HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

return gson.fromJson(httpResponse.body(), Emote[].class);
}

public UserResponse fetchUserEmoteSet(Provider provider, String userId) throws IOException, InterruptedException {
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(BASE_API + "/users/" + provider.path + "/" + userId))
.build();

HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

return gson.fromJson(httpResponse.body(), UserResponse.class);
}
}
11 changes: 11 additions & 0 deletions src/main/java/net/vinrobot/mcemote/api/bttv/Emote.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.vinrobot.mcemote.api.bttv;

public record Emote(
String id,
String code,
String imageType,
boolean animated,
int width,
int height
) {
}
12 changes: 12 additions & 0 deletions src/main/java/net/vinrobot/mcemote/api/bttv/Provider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.vinrobot.mcemote.api.bttv;

public enum Provider {
TWITCH("twitch"),
YOUTUBE("youtube");

public final String path;

Provider(String path) {
this.path = path;
}
}
9 changes: 9 additions & 0 deletions src/main/java/net/vinrobot/mcemote/api/bttv/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.vinrobot.mcemote.api.bttv;

public record User(
String id,
String name,
String displayName,
String providerId
) {
}
10 changes: 10 additions & 0 deletions src/main/java/net/vinrobot/mcemote/api/bttv/UserResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.vinrobot.mcemote.api.bttv;

public record UserResponse(
String id,
String[] bots,
String avatar,
Emote[] channelEmotes,
Emote[] sharedEmotes
) {
}

0 comments on commit f0f523b

Please sign in to comment.