From ac204c81698b125dbae9335450c235a47231d086 Mon Sep 17 00:00:00 2001 From: beanbeanjuice Date: Fri, 12 Jul 2024 12:07:36 -0400 Subject: [PATCH] Added the Twitch Live Listener --- .../com/beanbeanjuice/cafebot/CafeBot.java | 17 ++- .../commands/twitch/TwitchCommand.java | 83 ++++++++++++ .../TwitchChannelRemoveSubCommand.java | 45 +++++++ .../channel/TwitchChannelSetSubCommand.java | 67 ++++++++++ .../role/TwitchRoleRemoveSubCommand.java | 44 +++++++ .../twitch/role/TwitchRoleSetSubCommand.java | 56 ++++++++ .../twitch/user/TwitchAddUserSubCommand.java | 55 ++++++++ .../user/TwitchListUsersSubCommand.java | 63 +++++++++ .../user/TwitchRemoveUserSubCommand.java | 54 ++++++++ .../utility/commands/CommandCategory.java | 1 + .../utility/commands/CommandHandler.java | 6 +- .../utility/sections/generic/HelpHandler.java | 13 +- .../sections/twitch/TwitchHandler.java | 48 +++++++ .../twitch/TwitchLiveEventListener.java | 123 ++++++++++++++++++ 14 files changed, 666 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/beanbeanjuice/cafebot/commands/twitch/TwitchCommand.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/commands/twitch/channel/TwitchChannelRemoveSubCommand.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/commands/twitch/channel/TwitchChannelSetSubCommand.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/commands/twitch/role/TwitchRoleRemoveSubCommand.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/commands/twitch/role/TwitchRoleSetSubCommand.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchAddUserSubCommand.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchListUsersSubCommand.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchRemoveUserSubCommand.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/utility/sections/twitch/TwitchHandler.java create mode 100644 src/main/java/com/beanbeanjuice/cafebot/utility/sections/twitch/TwitchLiveEventListener.java diff --git a/src/main/java/com/beanbeanjuice/cafebot/CafeBot.java b/src/main/java/com/beanbeanjuice/cafebot/CafeBot.java index 9e735818..77d388fd 100644 --- a/src/main/java/com/beanbeanjuice/cafebot/CafeBot.java +++ b/src/main/java/com/beanbeanjuice/cafebot/CafeBot.java @@ -19,6 +19,7 @@ import com.beanbeanjuice.cafebot.commands.interaction.*; import com.beanbeanjuice.cafebot.commands.social.MemberCountCommand; import com.beanbeanjuice.cafebot.commands.social.vent.VentCommand; +import com.beanbeanjuice.cafebot.commands.twitch.TwitchCommand; import com.beanbeanjuice.cafebot.utility.commands.CommandHandler; import com.beanbeanjuice.cafebot.utility.helper.Helper; import com.beanbeanjuice.cafebot.utility.listeners.BotAddListener; @@ -32,6 +33,7 @@ import com.beanbeanjuice.cafebot.utility.sections.game.TicTacToeListener; import com.beanbeanjuice.cafebot.utility.sections.generic.HelpHandler; import com.beanbeanjuice.cafebot.utility.sections.generic.HelpListener; +import com.beanbeanjuice.cafebot.utility.sections.twitch.TwitchHandler; import com.sun.management.OperatingSystemMXBean; import lombok.Getter; import net.dv8tion.jda.api.EmbedBuilder; @@ -76,6 +78,7 @@ public class CafeBot { private CommandHandler commandHandler; @Getter private MenuHandler menuHandler; @Getter private HelpHandler helpHandler; + @Getter private TwitchHandler twitchHandler; // Additional Items @Getter private int commandsRun = 0; @@ -192,10 +195,6 @@ private void setupCommands() { new GameCommand(this), new TicTacToeCommand(this), - // Social - new MemberCountCommand(this), - new VentCommand(this), - // Interactions new AmazedCommand(this), new AskCommand(this), @@ -234,13 +233,21 @@ private void setupCommands() { new UWUCommand(this), new WaveCommand(this), new WinkCommand(this), - new YellCommand(this) + new YellCommand(this), + + // Social + new MemberCountCommand(this), + new VentCommand(this), + + // Twitch + new TwitchCommand(this) // new EmbedCommand(this) ); this.JDA.addEventListener(commandHandler); this.helpHandler = new HelpHandler(commandHandler); + this.twitchHandler = new TwitchHandler(System.getenv("CAFEBOT_TWITCH_ACCESS_TOKEN"), this); } private void setupListeners() { diff --git a/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/TwitchCommand.java b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/TwitchCommand.java new file mode 100644 index 00000000..2c7e85ac --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/TwitchCommand.java @@ -0,0 +1,83 @@ +package com.beanbeanjuice.cafebot.commands.twitch; + +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.commands.twitch.channel.TwitchChannelRemoveSubCommand; +import com.beanbeanjuice.cafebot.commands.twitch.channel.TwitchChannelSetSubCommand; +import com.beanbeanjuice.cafebot.commands.twitch.role.TwitchRoleRemoveSubCommand; +import com.beanbeanjuice.cafebot.commands.twitch.role.TwitchRoleSetSubCommand; +import com.beanbeanjuice.cafebot.commands.twitch.user.TwitchAddUserSubCommand; +import com.beanbeanjuice.cafebot.commands.twitch.user.TwitchListUsersSubCommand; +import com.beanbeanjuice.cafebot.commands.twitch.user.TwitchRemoveUserSubCommand; +import com.beanbeanjuice.cafebot.utility.commands.*; +import net.dv8tion.jda.api.Permission; + +public class TwitchCommand extends Command implements ICommand { + + public TwitchCommand(final CafeBot cafeBot) { + super(cafeBot); + } + + @Override + public String getName() { + return "twitch"; + } + + @Override + public String getDescription() { + return "Add or remove channels/notification channels."; + } + + @Override + public CommandCategory getCategory() { + return CommandCategory.TWITCH; + } + + @Override + public Permission[] getPermissions() { + return new Permission[] { + Permission.MANAGE_CHANNEL, + Permission.MANAGE_EVENTS, + Permission.MANAGE_SERVER + }; + } + + @Override + public boolean isEphemeral() { + return true; + } + + @Override + public boolean isNSFW() { + return false; + } + + @Override + public boolean allowDM() { + return false; + } + + @Override + public SubCommandGroup[] getSubCommandGroups() { + SubCommandGroup userSubCommandGroup = new SubCommandGroup("user", "Add, list, or remove twitch channels."); + userSubCommandGroup.addSubCommands(new ISubCommand[] { + new TwitchAddUserSubCommand(cafeBot), + new TwitchRemoveUserSubCommand(cafeBot), + new TwitchListUsersSubCommand(cafeBot) + }); + + SubCommandGroup roleSubCommandGroup = new SubCommandGroup("role", "Add or remove the live notifications role."); + roleSubCommandGroup.addSubCommands(new ISubCommand[] { + new TwitchRoleSetSubCommand(cafeBot), + new TwitchRoleRemoveSubCommand(cafeBot) + }); + + SubCommandGroup channelSubCommandGroup = new SubCommandGroup("channel", "Add or remove the live notifications channel."); + channelSubCommandGroup.addSubCommands(new ISubCommand[] { + new TwitchChannelSetSubCommand(cafeBot), + new TwitchChannelRemoveSubCommand(cafeBot) + }); + + return new SubCommandGroup[] { userSubCommandGroup, roleSubCommandGroup, channelSubCommandGroup }; + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/channel/TwitchChannelRemoveSubCommand.java b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/channel/TwitchChannelRemoveSubCommand.java new file mode 100644 index 00000000..bbd756ee --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/channel/TwitchChannelRemoveSubCommand.java @@ -0,0 +1,45 @@ +package com.beanbeanjuice.cafebot.commands.twitch.channel; + +import com.beanbeanjuice.cafeapi.wrapper.endpoints.guilds.GuildInformationType; +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.commands.Command; +import com.beanbeanjuice.cafebot.utility.commands.ISubCommand; +import com.beanbeanjuice.cafebot.utility.helper.Helper; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; + +public class TwitchChannelRemoveSubCommand extends Command implements ISubCommand { + + public TwitchChannelRemoveSubCommand(final CafeBot cafeBot) { + super(cafeBot); + } + + @Override + public void handle(SlashCommandInteractionEvent event) { + cafeBot.getCafeAPI().getGuildsEndpoint() + .updateGuildInformation(event.getGuild().getId(), GuildInformationType.TWITCH_CHANNEL_ID, "0") + .thenAcceptAsync((ignored) -> { + event.getHook().sendMessageEmbeds(Helper.successEmbed( + "Channel Removed", + "The twitch notifications channel has been successfully removed." + )).queue(); + }) + .exceptionallyAsync((e) -> { + event.getHook().sendMessageEmbeds(Helper.errorEmbed( + "Error Removing Channel", + String.format("There was an error removing the twitch notifications channel: %s", e.getMessage()) + )).queue(); + return null; + }); + } + + @Override + public String getName() { + return "remove"; + } + + @Override + public String getDescription() { + return "Remove the twitch notifications channel."; + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/channel/TwitchChannelSetSubCommand.java b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/channel/TwitchChannelSetSubCommand.java new file mode 100644 index 00000000..efa5957e --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/channel/TwitchChannelSetSubCommand.java @@ -0,0 +1,67 @@ +package com.beanbeanjuice.cafebot.commands.twitch.channel; + +import com.beanbeanjuice.cafeapi.wrapper.endpoints.guilds.GuildInformationType; +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.commands.Command; +import com.beanbeanjuice.cafebot.utility.commands.ISubCommand; +import com.beanbeanjuice.cafebot.utility.helper.Helper; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +import java.util.Optional; + +public class TwitchChannelSetSubCommand extends Command implements ISubCommand { + + public TwitchChannelSetSubCommand(final CafeBot cafeBot) { + super(cafeBot); + } + + @Override + public void handle(SlashCommandInteractionEvent event) { + Optional channelMapping = Optional.ofNullable(event.getOption("channel")); + + String channelID = channelMapping + .map(OptionMapping::getAsChannel) + .map(GuildChannelUnion::asTextChannel) + .map(TextChannel::getId) + .orElse(event.getChannel().getId()); + + cafeBot.getCafeAPI().getGuildsEndpoint() + .updateGuildInformation(event.getGuild().getId(), GuildInformationType.TWITCH_CHANNEL_ID, channelID) + .thenAcceptAsync((ignored) -> { + event.getHook().sendMessageEmbeds(Helper.successEmbed( + "Twitch Notifications Channel Set", + String.format("The live notifications channel has been successfully set to %s!", event.getGuild().getChannelById(TextChannel.class, channelID).getAsMention()) + )).queue(); + }) + .exceptionallyAsync((e) -> { + event.getHook().sendMessageEmbeds(Helper.errorEmbed( + "Error Setting Twitch Notifications Channel", + String.format("There was an error setting the twitch notifications channel: %s", e.getMessage()) + )).queue(); + return null; + }); + } + + @Override + public String getName() { + return "set"; + } + + @Override + public String getDescription() { + return "Set the twitch notifications channel."; + } + + @Override + public OptionData[] getOptions() { + return new OptionData[] { + new OptionData(OptionType.CHANNEL, "channel", "The channel to set the twitch channel to.", false) + }; + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/role/TwitchRoleRemoveSubCommand.java b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/role/TwitchRoleRemoveSubCommand.java new file mode 100644 index 00000000..6761c4dd --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/role/TwitchRoleRemoveSubCommand.java @@ -0,0 +1,44 @@ +package com.beanbeanjuice.cafebot.commands.twitch.role; + +import com.beanbeanjuice.cafeapi.wrapper.endpoints.guilds.GuildInformationType; +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.commands.Command; +import com.beanbeanjuice.cafebot.utility.commands.ISubCommand; +import com.beanbeanjuice.cafebot.utility.helper.Helper; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; + +public class TwitchRoleRemoveSubCommand extends Command implements ISubCommand { + + public TwitchRoleRemoveSubCommand(final CafeBot cafeBot) { + super(cafeBot); + } + + @Override + public void handle(SlashCommandInteractionEvent event) { + cafeBot.getCafeAPI().getGuildsEndpoint().updateGuildInformation(event.getGuild().getId(), GuildInformationType.LIVE_NOTIFICATIONS_ROLE_ID, "0") + .thenAcceptAsync((ignored) -> { + event.getHook().sendMessageEmbeds(Helper.successEmbed( + "Successfully Removed Live Notifications Role", + "The live notifications role has been successfully removed." + )).queue(); + }) + .exceptionallyAsync((e) -> { + event.getHook().sendMessageEmbeds(Helper.errorEmbed( + "Error Removing Live Notifications Role", + String.format("There was an error removing the live notifications role: %s", e.getMessage()) + )).queue(); + return null; + }); + } + + @Override + public String getName() { + return "remove"; + } + + @Override + public String getDescription() { + return "Remove the live notifications role."; + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/role/TwitchRoleSetSubCommand.java b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/role/TwitchRoleSetSubCommand.java new file mode 100644 index 00000000..df71cca3 --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/role/TwitchRoleSetSubCommand.java @@ -0,0 +1,56 @@ +package com.beanbeanjuice.cafebot.commands.twitch.role; + +import com.beanbeanjuice.cafeapi.wrapper.endpoints.guilds.GuildInformationType; +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.commands.Command; +import com.beanbeanjuice.cafebot.utility.commands.ISubCommand; +import com.beanbeanjuice.cafebot.utility.helper.Helper; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +public class TwitchRoleSetSubCommand extends Command implements ISubCommand { + + public TwitchRoleSetSubCommand(final CafeBot cafeBot) { + super(cafeBot); + } + + @Override + public void handle(SlashCommandInteractionEvent event) { + Role role = event.getOption("role").getAsRole(); // Should not be null. + + cafeBot.getCafeAPI().getGuildsEndpoint().updateGuildInformation(event.getGuild().getId(), GuildInformationType.LIVE_NOTIFICATIONS_ROLE_ID, role.getId()) + .thenAcceptAsync((ignored) -> { + event.getHook().sendMessageEmbeds(Helper.successEmbed( + "Live Notifications Role Set", + String.format("The live notifications role has been successfully set to %s.", role.getAsMention()) + )).queue(); + }) + .exceptionallyAsync((e) -> { + event.getHook().sendMessageEmbeds(Helper.errorEmbed( + "Error Setting Live Notification Role", + String.format("There was an error setting the live notification role: %s", e.getMessage()) + )).queue(); + return null; + }); + } + + @Override + public String getName() { + return "set"; + } + + @Override + public String getDescription() { + return "Set the live notification role!"; + } + + @Override + public OptionData[] getOptions() { + return new OptionData[] { + new OptionData(OptionType.ROLE, "role", "The live notification role you want to set.", true) + }; + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchAddUserSubCommand.java b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchAddUserSubCommand.java new file mode 100644 index 00000000..055af88e --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchAddUserSubCommand.java @@ -0,0 +1,55 @@ +package com.beanbeanjuice.cafebot.commands.twitch.user; + +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.commands.Command; +import com.beanbeanjuice.cafebot.utility.commands.ISubCommand; +import com.beanbeanjuice.cafebot.utility.helper.Helper; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +public class TwitchAddUserSubCommand extends Command implements ISubCommand { + + public TwitchAddUserSubCommand(final CafeBot cafeBot) { + super(cafeBot); + } + + @Override + public void handle(SlashCommandInteractionEvent event) { + String username = event.getOption("username").getAsString(); // Should not be null. + + cafeBot.getCafeAPI().getTwitchEndpoint().addGuildTwitch(event.getGuild().getId(), username) + .thenAcceptAsync((ignored) -> { + cafeBot.getTwitchHandler().addStream(username); + event.getHook().sendMessageEmbeds(Helper.successEmbed( + "Added Channel", + String.format("Successfully added **%s**.", username) + )).queue(); + }) + .exceptionallyAsync((e) -> { + event.getHook().sendMessageEmbeds(Helper.errorEmbed( + "Error Adding Channel", + String.format("There was an error adding **%s**: %s", username, e.getMessage()) + )).queue(); + return null; + }); + } + + @Override + public String getName() { + return "add"; + } + + @Override + public String getDescription() { + return "Add a twitch channel to listen for live notifications!"; + } + + @Override + public OptionData[] getOptions() { + return new OptionData[] { + new OptionData(OptionType.STRING, "username", "Their twitch username.", true) + }; + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchListUsersSubCommand.java b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchListUsersSubCommand.java new file mode 100644 index 00000000..dff3ec7a --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchListUsersSubCommand.java @@ -0,0 +1,63 @@ +package com.beanbeanjuice.cafebot.commands.twitch.user; + +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.commands.Command; +import com.beanbeanjuice.cafebot.utility.commands.ISubCommand; +import com.beanbeanjuice.cafebot.utility.helper.Helper; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; + +import java.util.List; + +public class TwitchListUsersSubCommand extends Command implements ISubCommand { + + public TwitchListUsersSubCommand(final CafeBot cafeBot) { + super(cafeBot); + } + + @Override + public void handle(SlashCommandInteractionEvent event) { + cafeBot.getCafeAPI().getTwitchEndpoint().getGuildTwitches(event.getGuild().getId()) + .thenAcceptAsync((channels) -> { + event.getHook().sendMessageEmbeds(twitchChannelsEmbed(channels)).queue(); + }) + .exceptionallyAsync((e) -> { + event.getHook().sendMessageEmbeds(Helper.errorEmbed( + "Error Getting Twitch Channels", + String.format("There was an error getting twitch channels for this server: %s", e.getMessage()) + )).queue(); + return null; + }); + } + + private MessageEmbed twitchChannelsEmbed(List channels) { + EmbedBuilder embedBuilder = new EmbedBuilder(); + embedBuilder.setTitle("Twitch Channels"); + + String channelsString = String.join("\n", channels); + + String description = String.format( + """ + These are the following channels that have been added for this server: + + %s + """, channelsString + ); + + embedBuilder.setDescription(description); + embedBuilder.setColor(Helper.getRandomColor()); + return embedBuilder.build(); + } + + @Override + public String getName() { + return "list"; + } + + @Override + public String getDescription() { + return "List the current twitch channels you have on the server."; + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchRemoveUserSubCommand.java b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchRemoveUserSubCommand.java new file mode 100644 index 00000000..2dc05f24 --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/commands/twitch/user/TwitchRemoveUserSubCommand.java @@ -0,0 +1,54 @@ +package com.beanbeanjuice.cafebot.commands.twitch.user; + +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.commands.Command; +import com.beanbeanjuice.cafebot.utility.commands.ISubCommand; +import com.beanbeanjuice.cafebot.utility.helper.Helper; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +public class TwitchRemoveUserSubCommand extends Command implements ISubCommand { + + public TwitchRemoveUserSubCommand(final CafeBot cafeBot) { + super(cafeBot); + } + + @Override + public void handle(SlashCommandInteractionEvent event) { + String username = event.getOption("username").getAsString(); // Should not be null. + + cafeBot.getCafeAPI().getTwitchEndpoint().removeGuildTwitch(event.getGuild().getId(), username) + .thenAcceptAsync((ignored) -> { + event.getHook().sendMessageEmbeds(Helper.successEmbed( + "Twitch Channel Removed", + "They were successfully removed. You should no longer receive live notifications from them." + )).queue(); + }) + .exceptionallyAsync((e) -> { + event.getHook().sendMessageEmbeds(Helper.errorEmbed( + "Error Removing Twitch Channel", + "There was an error removing them, you may still receive live notifications for their channel." + )).queue(); + return null; + }); + } + + @Override + public String getName() { + return "remove"; + } + + @Override + public String getDescription() { + return "Remove a twitch channel from this server."; + } + + @Override + public OptionData[] getOptions() { + return new OptionData[] { + new OptionData(OptionType.STRING, "username", "The username you want to remove.", true) + }; + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/utility/commands/CommandCategory.java b/src/main/java/com/beanbeanjuice/cafebot/utility/commands/CommandCategory.java index 0e4e61d7..35b244e7 100644 --- a/src/main/java/com/beanbeanjuice/cafebot/utility/commands/CommandCategory.java +++ b/src/main/java/com/beanbeanjuice/cafebot/utility/commands/CommandCategory.java @@ -9,6 +9,7 @@ public enum CommandCategory { GAME("Bored huh?", "https://cdn.beanbeanjuice.com/images/cafeBot/category_type/games.png"), GENERIC("Very basic...", "https://cdn.beanbeanjuice.com/images/cafeBot/category_type/generic.png"), INTERACTION("Hugs, waves, slaps, and more!", "https://cdn.beanbeanjuice.com/images/cafeBot/category_type/interaction.png"), + TWITCH("Commands used for twitch.", "https://cdn.beanbeanjuice.com/images/cafeBot/category_type/twitch.jpg"), SOCIAL("Hmm... I just need to let it out... you know?", "https://cdn.beanbeanjuice.com/images/cafeBot/category_type/social.gif"); @Getter private final String description; diff --git a/src/main/java/com/beanbeanjuice/cafebot/utility/commands/CommandHandler.java b/src/main/java/com/beanbeanjuice/cafebot/utility/commands/CommandHandler.java index dc60b439..22e0f655 100644 --- a/src/main/java/com/beanbeanjuice/cafebot/utility/commands/CommandHandler.java +++ b/src/main/java/com/beanbeanjuice/cafebot/utility/commands/CommandHandler.java @@ -209,15 +209,15 @@ private void handleSubCommandWithoutGroupAutoComplete(final String subCommandNam private void handleSubCommandAutoComplete(final ISubCommand subCommand, final String option, final CommandAutoCompleteInteractionEvent event) { List options = getOptions(subCommand.getAutoComplete().get(option), event.getFocusedOption().getValue()); - handleAUtoComplete(options, event); + handleAutoComplete(options, event); } private void handleCommandAutoComplete(final ICommand command, final String option, final CommandAutoCompleteInteractionEvent event) { List options = getOptions(command.getAutoComplete().get(option), event.getFocusedOption().getValue()); - handleAUtoComplete(options, event); + handleAutoComplete(options, event); } - private void handleAUtoComplete(final List options, final CommandAutoCompleteInteractionEvent event) { + private void handleAutoComplete(final List options, final CommandAutoCompleteInteractionEvent event) { event.replyChoices(options).queue(); } diff --git a/src/main/java/com/beanbeanjuice/cafebot/utility/sections/generic/HelpHandler.java b/src/main/java/com/beanbeanjuice/cafebot/utility/sections/generic/HelpHandler.java index acc5da16..8425d079 100644 --- a/src/main/java/com/beanbeanjuice/cafebot/utility/sections/generic/HelpHandler.java +++ b/src/main/java/com/beanbeanjuice/cafebot/utility/sections/generic/HelpHandler.java @@ -5,6 +5,7 @@ import com.beanbeanjuice.cafebot.utility.commands.ICommand; import com.beanbeanjuice.cafebot.utility.helper.Helper; import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.interactions.commands.build.OptionData; @@ -91,6 +92,9 @@ public MessageEmbed getCommandEmbed(final String commandName) { String optionsString = this.getOptionsString(command.getOptions()); optionsString = (optionsString.isBlank()) ? "*None*" : optionsString; + String permissionsString = this.getPermissionsString(command.getPermissions()); + permissionsString = (permissionsString.isBlank()) ? "*None*" : permissionsString; + String commandString = String.format( """ # /%s @@ -100,11 +104,14 @@ public MessageEmbed getCommandEmbed(final String commandName) { %s ### Subcommands %s + ### Required Permissions + %s """, command.getName(), command.getDescription(), optionsString, - subCommandsString + subCommandsString, + permissionsString ); return new EmbedBuilder() @@ -123,6 +130,10 @@ public String getOptionsString(final OptionData[] options) { }).collect(Collectors.joining(" ")); } + public String getPermissionsString(final Permission[] permissions) { + return Arrays.stream(permissions).map((permission) -> String.format("`%s`", permission.getName())).collect(Collectors.joining(", ")); + } + public StringSelectMenu getAllCategoriesSelectMenu(int index) { StringSelectMenu.Builder builder = StringSelectMenu.create("cafeBot:help:" + index); builder.addOption("ALL", "ALL"); diff --git a/src/main/java/com/beanbeanjuice/cafebot/utility/sections/twitch/TwitchHandler.java b/src/main/java/com/beanbeanjuice/cafebot/utility/sections/twitch/TwitchHandler.java new file mode 100644 index 00000000..9c6b4bf3 --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/utility/sections/twitch/TwitchHandler.java @@ -0,0 +1,48 @@ +package com.beanbeanjuice.cafebot.utility.sections.twitch; + +import com.beanbeanjuice.cafeapi.wrapper.CafeAPI; +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.logging.LogLevel; +import com.github.philippheuer.credentialmanager.domain.OAuth2Credential; +import com.github.philippheuer.events4j.simple.SimpleEventHandler; +import com.github.twitch4j.TwitchClient; +import com.github.twitch4j.TwitchClientBuilder; + +import java.util.HashSet; + +public class TwitchHandler { + + private final TwitchClient twitchClient; + + public TwitchHandler(final String token, final CafeBot cafeBot) { + twitchClient = TwitchClientBuilder.builder() + .withEnableHelix(true) + .withDefaultAuthToken(new OAuth2Credential("twitch", token)) +// .withDefaultEventHandler(TwitchLiveEventListener.class) + .build(); + +// twitchClient.getEventManager().registerEventHandler(new TwitchLiveEventListener(twitchClient, cafeBot)); + twitchClient.getEventManager().registerEventHandler(new SimpleEventHandler()); + twitchClient.getEventManager().getEventHandler(SimpleEventHandler.class).registerListener(new TwitchLiveEventListener(twitchClient, cafeBot)); + +// twitchClient.getEventManager().registerEventHandler(new SimpleEventHandler()); // TODO: CONFIRM THIS WORKS + + handleStartup(cafeBot); + } + + public void handleStartup(final CafeBot cafeBot) { + cafeBot.getCafeAPI().getTwitchEndpoint().getAllTwitches().thenAcceptAsync((twitchMap) -> { + HashSet channels = new HashSet<>(); + twitchMap.values().forEach(channels::addAll); + channels.forEach((channel) -> { + this.addStream(channel); + cafeBot.getLogger().log(TwitchHandler.class, LogLevel.DEBUG, String.format("Adding Twitch Channel: %s", channel), false, false); + }); + }); + } + + public void addStream(final String channelName) { + twitchClient.getClientHelper().enableStreamEventListener(channelName); + } + +} diff --git a/src/main/java/com/beanbeanjuice/cafebot/utility/sections/twitch/TwitchLiveEventListener.java b/src/main/java/com/beanbeanjuice/cafebot/utility/sections/twitch/TwitchLiveEventListener.java new file mode 100644 index 00000000..6dc44720 --- /dev/null +++ b/src/main/java/com/beanbeanjuice/cafebot/utility/sections/twitch/TwitchLiveEventListener.java @@ -0,0 +1,123 @@ +package com.beanbeanjuice.cafebot.utility.sections.twitch; + +import com.beanbeanjuice.cafeapi.wrapper.endpoints.guilds.GuildInformation; +import com.beanbeanjuice.cafeapi.wrapper.endpoints.guilds.GuildInformationType; +import com.beanbeanjuice.cafebot.CafeBot; +import com.beanbeanjuice.cafebot.utility.logging.LogLevel; +import com.github.philippheuer.events4j.simple.SimpleEventHandler; +import com.github.philippheuer.events4j.simple.domain.EventSubscriber; +import com.github.twitch4j.TwitchClient; +import com.github.twitch4j.events.ChannelGoLiveEvent; +import com.github.twitch4j.events.ChannelGoOfflineEvent; +import com.github.twitch4j.helix.domain.GameList; +import com.github.twitch4j.helix.domain.UserList; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class TwitchLiveEventListener extends SimpleEventHandler { + + private final TwitchClient twitchClient; + private final CafeBot cafeBot; + + public TwitchLiveEventListener(final TwitchClient twitchClient, final CafeBot cafeBot) { + this.twitchClient = twitchClient; + this.cafeBot = cafeBot; + } + + @EventSubscriber + public void onChannelGoLive(final ChannelGoLiveEvent event) { + // Converts the Twitch Name to lower case. + String twitchID = event.getChannel().getId(); + String gameID = event.getStream().getGameId(); + + cafeBot.getLogger().log(TwitchLiveEventListener.class, LogLevel.DEBUG, "LIVE: " + event.getChannel().getName()); + + CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> { + try { return twitchClient.getHelix().getUsers(null, List.of(twitchID), null).queue().get(); } + catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } + }); + + cafeBot.getLogger().log(TwitchLiveEventListener.class, LogLevel.DEBUG, "GAME ID: " + gameID); + if (gameID == null || gameID.isBlank()) { + userFuture.thenAcceptAsync((users) -> { + handleLiveEvent(event, users.getUsers().getFirst().getProfileImageUrl(), null); + }); + return; + } + + CompletableFuture gameFuture = CompletableFuture.supplyAsync(() -> { + try { return twitchClient.getHelix().getGames(null, List.of(gameID), null, null).queue().get(); } + catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } + }); + + userFuture.thenCombineAsync(gameFuture, (users, games) -> { + handleLiveEvent(event, users.getUsers().getFirst().getProfileImageUrl(), games.getGames().getFirst().getBoxArtUrl()); + return true; + }); + } + + // TODO: Add offline edit. + @EventSubscriber + public void onChannelGoOffline(final ChannelGoOfflineEvent event) { } + + private void handleLiveEvent(final ChannelGoLiveEvent event, final String profileImageURL, @Nullable final String boxArtURL) { + String twitchName = event.getChannel().getName().toLowerCase(); + + cafeBot.getCafeAPI().getTwitchEndpoint().getAllTwitches().thenApplyAsync(HashMap::entrySet).thenAcceptAsync((entries) -> { + entries.stream().filter((entry) -> entry.getValue().contains(twitchName)).forEach((entry) -> { + String guildID = entry.getKey(); + + cafeBot.getCafeAPI().getGuildsEndpoint().getGuildInformation(guildID).thenAcceptAsync((information) -> { + handleMessageToServers(event, profileImageURL, boxArtURL, twitchName, guildID, information); + }); + }); + }); + } + + private void handleMessageToServers(final ChannelGoLiveEvent event, final String profileImageURL, @Nullable final String boxArtURL, final String twitchName, final String guildID, final GuildInformation information) { + Guild guild = cafeBot.getJDA().getGuildById(guildID); + if (guild == null) return; + + TextChannel channel = guild.getTextChannelById(information.getSetting(GuildInformationType.TWITCH_CHANNEL_ID)); + if (channel == null) return; + + Optional role = Optional.ofNullable(guild.getRoleById(information.getSetting(GuildInformationType.LIVE_NOTIFICATIONS_ROLE_ID))); + String liveMessage = String.format("**%s** is live!", twitchName); + if (role.isPresent()) liveMessage += String.format(" %s", role.get().getAsMention()); + + channel.sendMessage(liveMessage).addEmbeds(liveEmbed(event, profileImageURL, boxArtURL, twitchName)).queue(); + cafeBot.increaseCommandsRun(); + } + + public MessageEmbed liveEmbed(final ChannelGoLiveEvent event, final String profileImageURL, @Nullable final String boxArtURL, final String twitchName) { + String link = String.format("https://www.twitch.tv/%s", twitchName); + String imageURL = event.getStream().getThumbnailUrl(1920, 1080); + + EmbedBuilder embedBuilder = new EmbedBuilder(); + + embedBuilder + .setColor(Color.pink) + .setAuthor(event.getChannel().getName(), null, profileImageURL) + .setTitle(event.getStream().getTitle(), link) + .setImage(imageURL) + .addField("Game", event.getStream().getGameName(), true) + .addField("Viewers", String.valueOf(event.getStream().getViewerCount()), true) + .setFooter("Live information brought to you by cafeBot!"); + + if (boxArtURL != null) embedBuilder.setThumbnail(boxArtURL); + + return embedBuilder.build(); + } + +}