add scoresaber feed command and websocket impl
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 53s
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 53s
This commit is contained in:
parent
39cdab27ce
commit
c0ae0fc596
19
pom.xml
19
pom.xml
@ -81,6 +81,10 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Libraries -->
|
<!-- Libraries -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -89,11 +93,6 @@
|
|||||||
<version>1.18.32</version>
|
<version>1.18.32</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
|
||||||
<artifactId>httpclient5</artifactId>
|
|
||||||
<version>5.3.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Dependencies -->
|
<!-- Dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -101,6 +100,16 @@
|
|||||||
<artifactId>JDA</artifactId>
|
<artifactId>JDA</artifactId>
|
||||||
<version>5.0.0-beta.24</version>
|
<version>5.0.0-beta.24</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||||
|
<artifactId>httpclient5</artifactId>
|
||||||
|
<version>5.3.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Test Dependencies -->
|
<!-- Test Dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package cc.fascinated.bat;
|
package cc.fascinated.bat;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
@ -14,6 +16,8 @@ import java.util.Objects;
|
|||||||
@SpringBootApplication()
|
@SpringBootApplication()
|
||||||
@Log4j2(topic = "Ember")
|
@Log4j2(topic = "Ember")
|
||||||
public class BatApplication {
|
public class BatApplication {
|
||||||
|
public static Gson GSON = new GsonBuilder().create();
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static void main(@NonNull String[] args) {
|
public static void main(@NonNull String[] args) {
|
||||||
// Handle loading of our configuration file
|
// Handle loading of our configuration file
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package cc.fascinated.bat.command;
|
package cc.fascinated.bat.command;
|
||||||
|
|
||||||
import cc.fascinated.bat.model.BatGuild;
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
import cc.fascinated.bat.model.user.BatUser;
|
import cc.fascinated.bat.model.user.BatUser;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import net.dv8tion.jda.api.entities.Member;
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
|
||||||
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,14 +20,12 @@ public interface BatCommandExecutor {
|
|||||||
* @param channel the channel the command was executed in
|
* @param channel the channel the command was executed in
|
||||||
* @param member the member that executed the command
|
* @param member the member that executed the command
|
||||||
* @param interaction the slash command interaction
|
* @param interaction the slash command interaction
|
||||||
* @param option the option that was used in the command, or null if no option was used
|
|
||||||
*/
|
*/
|
||||||
default void execute(
|
default void execute(
|
||||||
@NonNull BatGuild guild,
|
@NonNull BatGuild guild,
|
||||||
@NonNull BatUser user,
|
@NonNull BatUser user,
|
||||||
@NonNull TextChannel channel,
|
@NonNull TextChannel channel,
|
||||||
@NonNull Member member,
|
@NonNull Member member,
|
||||||
@NonNull SlashCommandInteraction interaction,
|
@NonNull SlashCommandInteraction interaction
|
||||||
OptionMapping option
|
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package cc.fascinated.bat.command.impl.global.beatsaber.scoresaber;
|
|||||||
|
|
||||||
import cc.fascinated.bat.command.BatSubCommand;
|
import cc.fascinated.bat.command.BatSubCommand;
|
||||||
import cc.fascinated.bat.common.EmbedUtils;
|
import cc.fascinated.bat.common.EmbedUtils;
|
||||||
import cc.fascinated.bat.model.BatGuild;
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken;
|
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken;
|
||||||
import cc.fascinated.bat.model.user.BatUser;
|
import cc.fascinated.bat.model.user.BatUser;
|
||||||
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
|
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
|
||||||
@ -31,7 +31,8 @@ public class LinkSubCommand extends BatSubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction, OptionMapping option) {
|
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
|
||||||
|
OptionMapping option = interaction.getOption("link");
|
||||||
if (option == null) {
|
if (option == null) {
|
||||||
interaction.replyEmbeds(EmbedUtils.buildErrorEmbed("Please provide a ScoreSaber profile link").build()).queue();
|
interaction.replyEmbeds(EmbedUtils.buildErrorEmbed("Please provide a ScoreSaber profile link").build()).queue();
|
||||||
return;
|
return;
|
||||||
|
@ -5,19 +5,21 @@ import cc.fascinated.bat.common.Colors;
|
|||||||
import cc.fascinated.bat.common.DateUtils;
|
import cc.fascinated.bat.common.DateUtils;
|
||||||
import cc.fascinated.bat.common.EmbedUtils;
|
import cc.fascinated.bat.common.EmbedUtils;
|
||||||
import cc.fascinated.bat.common.NumberUtils;
|
import cc.fascinated.bat.common.NumberUtils;
|
||||||
import cc.fascinated.bat.model.BatGuild;
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken;
|
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken;
|
||||||
import cc.fascinated.bat.model.user.BatUser;
|
import cc.fascinated.bat.model.user.BatUser;
|
||||||
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
|
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
|
||||||
import cc.fascinated.bat.service.ScoreSaberService;
|
import cc.fascinated.bat.service.ScoreSaberService;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import net.dv8tion.jda.api.EmbedBuilder;
|
import net.dv8tion.jda.api.EmbedBuilder;
|
||||||
|
import net.dv8tion.jda.api.Permission;
|
||||||
import net.dv8tion.jda.api.entities.Member;
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
|
||||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||||
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||||
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
|
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@ -37,15 +39,31 @@ public class ScoreSaberCommand extends BatCommand {
|
|||||||
super.setCategory(Category.BEAT_SABER);
|
super.setCategory(Category.BEAT_SABER);
|
||||||
this.scoreSaberService = scoreSaberService;
|
this.scoreSaberService = scoreSaberService;
|
||||||
|
|
||||||
super.setDescription("View a user's ScoreSaber profile");
|
super.setDescription("General ScoreSaber commands");
|
||||||
super.setCommandData(new CommandDataImpl(this.getName(), this.getDescription())
|
super.setCommandData(new CommandDataImpl(this.getName(), this.getDescription())
|
||||||
.addOptions(new OptionData(OptionType.STRING, "link", "Link your ScoreSaber profile", false))
|
.addSubcommands(new SubcommandData("link", "Link your ScoreSaber profile")
|
||||||
.addOptions(new OptionData(OptionType.USER, "user", "The user to view the ScoreSaber profile of", false))
|
.addOptions(new OptionData(OptionType.STRING, "link", "Link your ScoreSaber profile", true))
|
||||||
|
)
|
||||||
|
|
||||||
|
.addSubcommands(new SubcommandData("user", "View a user's ScoreSaber profile")
|
||||||
|
.addOptions(new OptionData(OptionType.USER, "user", "The user to view the ScoreSaber profile of", true))
|
||||||
|
)
|
||||||
|
|
||||||
|
.addSubcommands(new SubcommandData("score-feed-user", "Edit your ScoreSaber score feed settings")
|
||||||
|
.addOptions(new OptionData(OptionType.USER, "user", "Add or remove a user from the score feed", false))
|
||||||
|
).setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
|
||||||
|
|
||||||
|
.addSubcommands(new SubcommandData("score-feed-clear-users", "Remove all users from the score feed"))
|
||||||
|
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
|
||||||
|
|
||||||
|
.addSubcommands(new SubcommandData("score-feed-channel", "Edit your ScoreSaber score feed settings")
|
||||||
|
.addOptions(new OptionData(OptionType.CHANNEL, "channel", "Set the channel to send the score feed in", false))
|
||||||
|
).setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction, OptionMapping option) {
|
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
|
||||||
sendProfileEmbed(true, user, scoreSaberService, interaction);
|
sendProfileEmbed(true, user, scoreSaberService, interaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package cc.fascinated.bat.command.impl.global.beatsaber.scoresaber;
|
package cc.fascinated.bat.command.impl.global.beatsaber.scoresaber;
|
||||||
|
|
||||||
import cc.fascinated.bat.command.BatSubCommand;
|
import cc.fascinated.bat.command.BatSubCommand;
|
||||||
import cc.fascinated.bat.model.BatGuild;
|
import cc.fascinated.bat.common.EmbedUtils;
|
||||||
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
import cc.fascinated.bat.model.user.BatUser;
|
import cc.fascinated.bat.model.user.BatUser;
|
||||||
import cc.fascinated.bat.service.ScoreSaberService;
|
import cc.fascinated.bat.service.ScoreSaberService;
|
||||||
import cc.fascinated.bat.service.UserService;
|
import cc.fascinated.bat.service.UserService;
|
||||||
@ -28,13 +29,23 @@ public class UserSubCommand extends BatSubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction, OptionMapping option) {
|
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
|
||||||
BatUser target = userService.getUser(option.getAsUser().getId());
|
OptionMapping option = interaction.getOption("user");
|
||||||
if (target == null) {
|
if (option == null) {
|
||||||
interaction.reply("User not found").queue();
|
interaction.replyEmbeds(EmbedUtils.buildErrorEmbed("Please provide a user to view the ScoreSaber profile of").build()).queue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (option.getAsUser().isBot()) {
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildErrorEmbed("You cannot view the ScoreSaber profile for a Bot").build()).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BatUser target = userService.getUser(option.getAsUser().getId());
|
||||||
|
if (target == null) {
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildErrorEmbed("Unknown user").build()).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
ScoreSaberCommand.sendProfileEmbed(false, target, scoreSaberService, interaction);
|
ScoreSaberCommand.sendProfileEmbed(false, target, scoreSaberService, interaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
58
src/main/java/cc/fascinated/bat/command/impl/guild/beatsaber/scoresaber/ScoreFeedChannelCommand.java
Normal file
58
src/main/java/cc/fascinated/bat/command/impl/guild/beatsaber/scoresaber/ScoreFeedChannelCommand.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber;
|
||||||
|
|
||||||
|
import cc.fascinated.bat.command.BatSubCommand;
|
||||||
|
import cc.fascinated.bat.common.EmbedUtils;
|
||||||
|
import cc.fascinated.bat.common.TextChannelUtils;
|
||||||
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
|
import cc.fascinated.bat.model.guild.profiles.ScoreSaberScoreFeedProfile;
|
||||||
|
import cc.fascinated.bat.model.user.BatUser;
|
||||||
|
import cc.fascinated.bat.service.GuildService;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ScoreFeedChannelCommand extends BatSubCommand {
|
||||||
|
private final GuildService guildService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ScoreFeedChannelCommand(GuildService guildService) {
|
||||||
|
this.guildService = guildService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
|
||||||
|
ScoreSaberScoreFeedProfile profile = guild.getProfile(ScoreSaberScoreFeedProfile.class);
|
||||||
|
OptionMapping option = interaction.getOption("channel");
|
||||||
|
if (option == null) {
|
||||||
|
if (!TextChannelUtils.isValidChannel(profile.getChannelId())) {
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildErrorEmbed("Please provide a channel to set the ScoreSaber feed channel to").build()).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildSuccessEmbed("The current ScoreSaber feed channel is %s"
|
||||||
|
.formatted(TextChannelUtils.getChannelMention(profile.getChannelId()))).build()).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuildChannelUnion targetChannel = option.getAsChannel();
|
||||||
|
if (targetChannel.getType() != ChannelType.TEXT) {
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildErrorEmbed("Invalid channel type, please provide a text channel").build()).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.setChannelId(targetChannel.getId());
|
||||||
|
guildService.saveGuild(guild);
|
||||||
|
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildSuccessEmbed("Successfully set the ScoreSaber feed channel to %s"
|
||||||
|
.formatted(targetChannel.asTextChannel().getAsMention())).build()).queue();
|
||||||
|
}
|
||||||
|
}
|
40
src/main/java/cc/fascinated/bat/command/impl/guild/beatsaber/scoresaber/ScoreFeedClearUsersCommand.java
Normal file
40
src/main/java/cc/fascinated/bat/command/impl/guild/beatsaber/scoresaber/ScoreFeedClearUsersCommand.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber;
|
||||||
|
|
||||||
|
import cc.fascinated.bat.command.BatSubCommand;
|
||||||
|
import cc.fascinated.bat.common.EmbedUtils;
|
||||||
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
|
import cc.fascinated.bat.model.guild.profiles.ScoreSaberScoreFeedProfile;
|
||||||
|
import cc.fascinated.bat.model.user.BatUser;
|
||||||
|
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
|
||||||
|
import cc.fascinated.bat.service.GuildService;
|
||||||
|
import cc.fascinated.bat.service.UserService;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
import net.dv8tion.jda.api.entities.User;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ScoreFeedClearUsersCommand extends BatSubCommand {
|
||||||
|
private final GuildService guildService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ScoreFeedClearUsersCommand(GuildService guildService, UserService userService) {
|
||||||
|
this.guildService = guildService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
|
||||||
|
ScoreSaberScoreFeedProfile profile = guild.getProfile(ScoreSaberScoreFeedProfile.class);
|
||||||
|
profile.getTrackedUsers().clear();
|
||||||
|
guildService.saveGuild(guild);
|
||||||
|
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildSuccessEmbed("Successfully cleared all users from the ScoreSaber feed").build()).queue();
|
||||||
|
}
|
||||||
|
}
|
72
src/main/java/cc/fascinated/bat/command/impl/guild/beatsaber/scoresaber/ScoreFeedUserCommand.java
Normal file
72
src/main/java/cc/fascinated/bat/command/impl/guild/beatsaber/scoresaber/ScoreFeedUserCommand.java
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber;
|
||||||
|
|
||||||
|
import cc.fascinated.bat.command.BatSubCommand;
|
||||||
|
import cc.fascinated.bat.common.EmbedUtils;
|
||||||
|
import cc.fascinated.bat.common.Profile;
|
||||||
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
|
import cc.fascinated.bat.model.guild.profiles.ScoreSaberScoreFeedProfile;
|
||||||
|
import cc.fascinated.bat.model.user.BatUser;
|
||||||
|
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
|
||||||
|
import cc.fascinated.bat.service.GuildService;
|
||||||
|
import cc.fascinated.bat.service.UserService;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
import net.dv8tion.jda.api.entities.User;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ScoreFeedUserCommand extends BatSubCommand {
|
||||||
|
private final GuildService guildService;
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ScoreFeedUserCommand(GuildService guildService, UserService userService) {
|
||||||
|
this.guildService = guildService;
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
|
||||||
|
ScoreSaberScoreFeedProfile profile = guild.getProfile(ScoreSaberScoreFeedProfile.class);
|
||||||
|
OptionMapping option = interaction.getOption("user");
|
||||||
|
if (option == null){
|
||||||
|
if (profile.getTrackedUsers().isEmpty()) {
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildGenericEmbed("There are no users being tracked in the ScoreSaber feed").build()).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
for (String accountId : profile.getTrackedUsers()) {
|
||||||
|
stringBuilder.append("[%s](%s)".formatted(
|
||||||
|
accountId,
|
||||||
|
"https://scoresaber.com/u/%s".formatted(accountId)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildGenericEmbed("The current users being tracked in the ScoreSaber feed are:\n%s".formatted(stringBuilder.toString())).build()).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User target = option.getAsUser();
|
||||||
|
BatUser targetUser = userService.getUser(target.getId());
|
||||||
|
ScoreSaberProfile targetProfile = targetUser.getProfile(ScoreSaberProfile.class);
|
||||||
|
if (targetProfile.getId() == null) {
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildErrorEmbed("The user you are trying to track does not have a linked ScoreSaber profile").build()).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.getTrackedUsers().contains(targetProfile.getId())) {
|
||||||
|
profile.getTrackedUsers().remove(targetProfile.getId());
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildSuccessEmbed("Successfully removed %s from the ScoreSaber feed".formatted(target.getAsMention())).build()).queue();
|
||||||
|
} else {
|
||||||
|
profile.getTrackedUsers().add(targetProfile.getId());
|
||||||
|
interaction.replyEmbeds(EmbedUtils.buildSuccessEmbed("Successfully added %s to the ScoreSaber feed".formatted(target.getAsMention())).build()).queue();
|
||||||
|
}
|
||||||
|
guildService.saveGuild(guild);
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ public class NumberUtils {
|
|||||||
public static String formatNumberCommas(double number) {
|
public static String formatNumberCommas(double number) {
|
||||||
NumberFormat format = NumberFormat.getNumberInstance();
|
NumberFormat format = NumberFormat.getNumberInstance();
|
||||||
format.setGroupingUsed(true);
|
format.setGroupingUsed(true);
|
||||||
|
format.setMaximumFractionDigits(2);
|
||||||
return format.format(number);
|
return format.format(number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
src/main/java/cc/fascinated/bat/common/ScoreSaberUtils.java
Normal file
23
src/main/java/cc/fascinated/bat/common/ScoreSaberUtils.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package cc.fascinated.bat.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
public class ScoreSaberUtils {
|
||||||
|
/**
|
||||||
|
* Gets the formatted difficulty of a song.
|
||||||
|
*
|
||||||
|
* @param difficulty the difficulty to format
|
||||||
|
* @return the formatted difficulty
|
||||||
|
*/
|
||||||
|
public static String getFormattedDifficulty(int difficulty) {
|
||||||
|
return switch (difficulty) {
|
||||||
|
case 1 -> "Easy";
|
||||||
|
case 3 -> "Normal";
|
||||||
|
case 5 -> "Hard";
|
||||||
|
case 7 -> "Expert";
|
||||||
|
case 8 -> "Expert+";
|
||||||
|
default -> "Unknown";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
31
src/main/java/cc/fascinated/bat/common/TextChannelUtils.java
Normal file
31
src/main/java/cc/fascinated/bat/common/TextChannelUtils.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package cc.fascinated.bat.common;
|
||||||
|
|
||||||
|
import cc.fascinated.bat.service.DiscordService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
public class TextChannelUtils {
|
||||||
|
/**
|
||||||
|
* Checks if a channel is valid
|
||||||
|
*
|
||||||
|
* @param id the id of the channel
|
||||||
|
* @return if the channel is valid
|
||||||
|
*/
|
||||||
|
public static boolean isValidChannel(String id) {
|
||||||
|
if (id == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return DiscordService.JDA.getTextChannelById(id) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the mention of a channel
|
||||||
|
*
|
||||||
|
* @param id the id of the channel
|
||||||
|
* @return the mention of the channel
|
||||||
|
*/
|
||||||
|
public static String getChannelMention(String id) {
|
||||||
|
return "<#" + id + ">";
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
package cc.fascinated.bat.model;
|
|
||||||
|
|
||||||
import cc.fascinated.bat.service.DiscordService;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import net.dv8tion.jda.api.entities.Guild;
|
|
||||||
import org.springframework.data.annotation.Id;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Fascinated (fascinated7)
|
|
||||||
*/
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Getter @Setter
|
|
||||||
@Document(collection = "guilds")
|
|
||||||
public class BatGuild {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the guild
|
|
||||||
*/
|
|
||||||
@NonNull @Id private final String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the guild as the JDA Guild
|
|
||||||
*
|
|
||||||
* @return the guild
|
|
||||||
*/
|
|
||||||
private Guild getDiscordGuild() {
|
|
||||||
return DiscordService.JDA.getGuildById(id);
|
|
||||||
}
|
|
||||||
}
|
|
66
src/main/java/cc/fascinated/bat/model/guild/BatGuild.java
Normal file
66
src/main/java/cc/fascinated/bat/model/guild/BatGuild.java
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package cc.fascinated.bat.model.guild;
|
||||||
|
|
||||||
|
import cc.fascinated.bat.common.Profile;
|
||||||
|
import cc.fascinated.bat.service.DiscordService;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter @Setter
|
||||||
|
@Document(collection = "guilds")
|
||||||
|
public class BatGuild {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the guild
|
||||||
|
*/
|
||||||
|
@NonNull @Id private final String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The profiles for this guild
|
||||||
|
*/
|
||||||
|
private Map<String, Profile> profiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the profile for the guild
|
||||||
|
*
|
||||||
|
* @param clazz The class of the profile
|
||||||
|
* @param <T> The type of the profile
|
||||||
|
* @return The profile
|
||||||
|
*/
|
||||||
|
public <T extends Profile> T getProfile(Class<?> clazz) {
|
||||||
|
if (profiles == null) {
|
||||||
|
profiles = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile profile = profiles.values().stream().filter(p -> p.getClass().equals(clazz)).findFirst().orElse(null);
|
||||||
|
if (profile == null) {
|
||||||
|
try {
|
||||||
|
profile = (Profile) clazz.newInstance();
|
||||||
|
profiles.put(profile.getProfileKey(), profile);
|
||||||
|
} catch (InstantiationException | IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T) profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the guild as the JDA Guild
|
||||||
|
*
|
||||||
|
* @return the guild
|
||||||
|
*/
|
||||||
|
public Guild getDiscordGuild() {
|
||||||
|
return DiscordService.JDA.getGuildById(id);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package cc.fascinated.bat.model.guild.profiles;
|
||||||
|
|
||||||
|
import cc.fascinated.bat.common.Profile;
|
||||||
|
import cc.fascinated.bat.service.DiscordService;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@Getter @Setter
|
||||||
|
public class ScoreSaberScoreFeedProfile extends Profile {
|
||||||
|
public ScoreSaberScoreFeedProfile() {
|
||||||
|
super("scoresaber-score-feed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The channel ID of the score feed
|
||||||
|
*/
|
||||||
|
private String channelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The users that are being tracked
|
||||||
|
*/
|
||||||
|
private List<String> trackedUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the tracked users
|
||||||
|
*
|
||||||
|
* @return the tracked users
|
||||||
|
*/
|
||||||
|
public List<String> getTrackedUsers() {
|
||||||
|
if (this.trackedUsers == null) {
|
||||||
|
this.trackedUsers = new ArrayList<>();
|
||||||
|
}
|
||||||
|
return this.trackedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a user to be tracked
|
||||||
|
*
|
||||||
|
* @param userId the user ID to add
|
||||||
|
*/
|
||||||
|
public void addTrackedUser(String userId) {
|
||||||
|
if (this.trackedUsers == null) {
|
||||||
|
this.trackedUsers = new ArrayList<>();
|
||||||
|
}
|
||||||
|
trackedUsers.add(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a user from being tracked
|
||||||
|
*
|
||||||
|
* @param userId the user ID to remove
|
||||||
|
*/
|
||||||
|
public void removeTrackedUser(String userId) {
|
||||||
|
if (this.trackedUsers == null) {
|
||||||
|
this.trackedUsers = new ArrayList<>();
|
||||||
|
}
|
||||||
|
trackedUsers.remove(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the channel as a TextChannel
|
||||||
|
*
|
||||||
|
* @return the channel as a TextChannel
|
||||||
|
*/
|
||||||
|
public TextChannel getAsTextChannel() {
|
||||||
|
return DiscordService.JDA.getTextChannelById(channelId);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package cc.fascinated.bat.repository;
|
package cc.fascinated.bat.repository;
|
||||||
|
|
||||||
import cc.fascinated.bat.model.BatGuild;
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,8 +5,11 @@ import cc.fascinated.bat.command.BatSubCommand;
|
|||||||
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.LinkSubCommand;
|
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.LinkSubCommand;
|
||||||
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.ScoreSaberCommand;
|
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.ScoreSaberCommand;
|
||||||
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.UserSubCommand;
|
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.UserSubCommand;
|
||||||
|
import cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber.ScoreFeedChannelCommand;
|
||||||
|
import cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber.ScoreFeedClearUsersCommand;
|
||||||
|
import cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber.ScoreFeedUserCommand;
|
||||||
import cc.fascinated.bat.common.EmbedUtils;
|
import cc.fascinated.bat.common.EmbedUtils;
|
||||||
import cc.fascinated.bat.model.BatGuild;
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
import cc.fascinated.bat.model.user.BatUser;
|
import cc.fascinated.bat.model.user.BatUser;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
@ -14,7 +17,6 @@ import net.dv8tion.jda.api.JDA;
|
|||||||
import net.dv8tion.jda.api.entities.Guild;
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
@ -22,7 +24,6 @@ import org.springframework.context.annotation.DependsOn;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,8 +65,11 @@ public class CommandService extends ListenerAdapter {
|
|||||||
// Global commands
|
// Global commands
|
||||||
registerCommand(context.getBean(ScoreSaberCommand.class)
|
registerCommand(context.getBean(ScoreSaberCommand.class)
|
||||||
.addSubCommand("link", context.getBean(LinkSubCommand.class))
|
.addSubCommand("link", context.getBean(LinkSubCommand.class))
|
||||||
.addSubCommand("user", context.getBean(UserSubCommand.class)
|
.addSubCommand("user", context.getBean(UserSubCommand.class))
|
||||||
));
|
.addSubCommand("score-feed-user", context.getBean(ScoreFeedUserCommand.class))
|
||||||
|
.addSubCommand("score-feed-channel", context.getBean(ScoreFeedChannelCommand.class))
|
||||||
|
.addSubCommand("score-feed-clear-users", context.getBean(ScoreFeedClearUsersCommand.class))
|
||||||
|
);
|
||||||
|
|
||||||
registerSlashCommands(); // Register all slash commands
|
registerSlashCommands(); // Register all slash commands
|
||||||
}
|
}
|
||||||
@ -125,18 +129,13 @@ public class CommandService extends ListenerAdapter {
|
|||||||
BatUser user = userService.getUser(event.getUser().getId());
|
BatUser user = userService.getUser(event.getUser().getId());
|
||||||
|
|
||||||
// No args provided, use the main command executor
|
// No args provided, use the main command executor
|
||||||
List<OptionMapping> options = event.getInteraction().getOptions();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (options.isEmpty()) {
|
if (event.getInteraction().getSubcommandName() == null) {
|
||||||
command.execute(guild, user, event.getChannel().asTextChannel(), event.getMember(), event.getInteraction(), null);
|
command.execute(guild, user, event.getChannel().asTextChannel(), event.getMember(), event.getInteraction());
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// Check if the sub command exists
|
|
||||||
for (Map.Entry<String, BatSubCommand> subCommand : command.getSubCommands().entrySet()) {
|
for (Map.Entry<String, BatSubCommand> subCommand : command.getSubCommands().entrySet()) {
|
||||||
for (OptionMapping option : options) {
|
if (subCommand.getKey().equalsIgnoreCase(event.getInteraction().getSubcommandName())) {
|
||||||
if (subCommand.getKey().equalsIgnoreCase(option.getName())) {
|
subCommand.getValue().execute(guild, user, event.getChannel().asTextChannel(), event.getMember(), event.getInteraction());
|
||||||
subCommand.getValue().execute(guild, user, event.getChannel().asTextChannel(), event.getMember(), event.getInteraction(), option);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package cc.fascinated.bat.service;
|
package cc.fascinated.bat.service;
|
||||||
|
|
||||||
import cc.fascinated.bat.model.BatGuild;
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
import cc.fascinated.bat.repository.GuildRepository;
|
import cc.fascinated.bat.repository.GuildRepository;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
@ -1,27 +1,47 @@
|
|||||||
package cc.fascinated.bat.service;
|
package cc.fascinated.bat.service;
|
||||||
|
|
||||||
import cc.fascinated.bat.common.DateUtils;
|
import cc.fascinated.bat.BatApplication;
|
||||||
import cc.fascinated.bat.common.WebRequest;
|
import cc.fascinated.bat.common.*;
|
||||||
import cc.fascinated.bat.exception.BadRequestException;
|
import cc.fascinated.bat.exception.BadRequestException;
|
||||||
import cc.fascinated.bat.exception.ResourceNotFoundException;
|
import cc.fascinated.bat.exception.ResourceNotFoundException;
|
||||||
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken;
|
import cc.fascinated.bat.model.beatsaber.scoresaber.*;
|
||||||
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberPageMetadataToken;
|
import cc.fascinated.bat.model.guild.BatGuild;
|
||||||
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberPlayerScoreToken;
|
import cc.fascinated.bat.model.guild.profiles.ScoreSaberScoreFeedProfile;
|
||||||
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberScoresPageToken;
|
|
||||||
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
|
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import net.dv8tion.jda.api.EmbedBuilder;
|
||||||
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.socket.CloseStatus;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
||||||
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service @Log4j2(topic = "ScoreSaber Service")
|
@Service @Log4j2(topic = "ScoreSaber Service")
|
||||||
public class ScoreSaberService {
|
public class ScoreSaberService extends TextWebSocketHandler {
|
||||||
|
private final GuildService guildService;
|
||||||
|
|
||||||
private static final String SCORESABER_API = "https://scoresaber.com/api/";
|
private static final String SCORESABER_API = "https://scoresaber.com/api/";
|
||||||
private static final String GET_PLAYER_ENDPOINT = SCORESABER_API + "player/%s/full";
|
private static final String GET_PLAYER_ENDPOINT = SCORESABER_API + "player/%s/full";
|
||||||
private static final String GET_PLAYER_SCORES_ENDPOINT = SCORESABER_API + "player/%s/scores?limit=100&sort=%s&page=%s&withMetadata=true";
|
private static final String GET_PLAYER_SCORES_ENDPOINT = SCORESABER_API + "player/%s/scores?limit=100&sort=%s&page=%s&withMetadata=true";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ScoreSaberService(@NonNull GuildService guildService) {
|
||||||
|
this.guildService = guildService;
|
||||||
|
connectWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the account from the ScoreSaber API.
|
* Gets the account from the ScoreSaber API.
|
||||||
*
|
*
|
||||||
@ -82,4 +102,101 @@ public class ScoreSaberService {
|
|||||||
|
|
||||||
return scores;
|
return scores;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an embed for a score.
|
||||||
|
*
|
||||||
|
* @param score The score.
|
||||||
|
* @return The embed.
|
||||||
|
*/
|
||||||
|
public MessageEmbed buildScoreEmbed(ScoreSaberPlayerScoreToken score) {
|
||||||
|
ScoreSaberScoreToken scoreToken = score.getScore();
|
||||||
|
ScoreSaberLeaderboardToken leaderboardToken = score.getLeaderboard();
|
||||||
|
ScoreSaberScoreToken.LeaderboardPlayerInfo playerInfo = scoreToken.getLeaderboardPlayerInfo();
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.setAuthor(playerInfo.getName() + " just set a new score!", "https://scoresaber.com/u/%s".formatted(playerInfo.getId()),
|
||||||
|
"https://cdn.scoresaber.com/avatars/%s.jpg".formatted(playerInfo.getId()))
|
||||||
|
.setDescription("**%s** (%s%s)\n[[Map Link]](%s) [[SS Profile]](%s)".formatted(
|
||||||
|
leaderboardToken.getSongName(),
|
||||||
|
ScoreSaberUtils.getFormattedDifficulty(leaderboardToken.getDifficulty().getDifficulty()),
|
||||||
|
leaderboardToken.isRanked() ? " " + leaderboardToken.getStars() + "⭐" : "",
|
||||||
|
"https://scoresaber.com/leaderboard/%s".formatted(leaderboardToken.getId()),
|
||||||
|
"https://scoresaber.com/u/%s".formatted(playerInfo.getId())
|
||||||
|
))
|
||||||
|
.addField("Accuracy", "%s%%".formatted(
|
||||||
|
leaderboardToken.getMaxScore() == 0 ? "N/A" : NumberUtils.formatNumberCommas(((double) scoreToken.getBaseScore() / leaderboardToken.getMaxScore()) * 100)
|
||||||
|
), true)
|
||||||
|
.addField("Raw PP", scoreToken.getPp() == 0 ? "Unranked" : NumberUtils.formatNumberCommas(scoreToken.getPp()), true)
|
||||||
|
.addField("Global Rank", "#%s".formatted(NumberUtils.formatNumberCommas(scoreToken.getRank())), true)
|
||||||
|
.addField("Misses", "%s".formatted(scoreToken.getMissedNotes()), true)
|
||||||
|
.addField("Bad Cuts", "%s".formatted(scoreToken.getBadCuts()), true)
|
||||||
|
.addField("Max Combo", "%s %s".formatted(
|
||||||
|
scoreToken.getMaxCombo(),
|
||||||
|
scoreToken.getMaxCombo() == leaderboardToken.getMaxScore() ? "(FC)" : ""
|
||||||
|
), true)
|
||||||
|
.setColor(Colors.DEFAULT)
|
||||||
|
.setTimestamp(DateUtils.getDateFromString(scoreToken.getTimeSet()).toInstant())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the ScoreSaber WebSocket.
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
private void connectWebSocket() {
|
||||||
|
log.info("Connecting to the ScoreSaber WSS.");
|
||||||
|
new StandardWebSocketClient().execute(this, "wss://scoresaber.com/ws").get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionEstablished(@NonNull WebSocketSession session) {
|
||||||
|
log.info("Connected to the ScoreSaber WSS.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus status) {
|
||||||
|
log.info("Disconnected from the ScoreSaber WSS.");
|
||||||
|
connectWebSocket(); // Reconnect to the WebSocket.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @SneakyThrows
|
||||||
|
protected void handleTextMessage(@NonNull WebSocketSession session, @NonNull TextMessage message) {
|
||||||
|
// Ignore the connection message.
|
||||||
|
if (message.getPayload().equals("Connected to the ScoreSaber WSS")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JsonObject json = BatApplication.GSON.fromJson(message.getPayload(), JsonObject.class);
|
||||||
|
String command = json.get("commandName").getAsString();
|
||||||
|
JsonObject data = json.get("commandData").getAsJsonObject();
|
||||||
|
|
||||||
|
if (command.equals("score")) {
|
||||||
|
ScoreSaberPlayerScoreToken score = BatApplication.GSON.fromJson(data, ScoreSaberPlayerScoreToken.class);
|
||||||
|
ScoreSaberScoreToken.LeaderboardPlayerInfo playerInfo = score.getScore().getLeaderboardPlayerInfo();
|
||||||
|
|
||||||
|
for (Guild guild : DiscordService.JDA.getGuilds()) {
|
||||||
|
BatGuild batGuild = guildService.getGuild(guild.getId());
|
||||||
|
if (batGuild == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScoreSaberScoreFeedProfile profile = batGuild.getProfile(ScoreSaberScoreFeedProfile.class);
|
||||||
|
if (profile == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (profile.getChannelId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!profile.getTrackedUsers().contains(playerInfo.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.getAsTextChannel().sendMessageEmbeds(this.buildScoreEmbed(score)).queue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("An error occurred while handling the message.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user