update birthdays to have a view command and a private command to be able to hide your birthday
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s

This commit is contained in:
Lee 2024-06-30 00:35:52 +01:00
parent 86c7afac42
commit 29affe2f12
18 changed files with 259 additions and 42 deletions

18
pom.xml

@ -90,6 +90,24 @@
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.10.0</version>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-bom</artifactId>
<version>5.2.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-springboot-v3</artifactId>
<version>5.2.4</version>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongodb-springdata-v4-driver</artifactId>
<version>5.2.4</version>
</dependency>
<!-- Libraries -->
<dependency>

@ -2,6 +2,7 @@ package cc.fascinated.bat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.mongock.runner.springboot.EnableMongock;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
@ -16,6 +17,7 @@ import java.util.Objects;
@EnableScheduling
@SpringBootApplication
@EnableMongock
@Log4j2(topic = "Bat")
public class BatApplication {
public static Gson GSON = new GsonBuilder().create();

@ -0,0 +1,43 @@
package cc.fascinated.bat.changelog;
import com.mongodb.client.FindIterable;
import io.mongock.api.annotations.ChangeUnit;
import io.mongock.api.annotations.Execution;
import io.mongock.api.annotations.RollbackExecution;
import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
import javax.print.Doc;
/**
* @author Fascinated (fascinated7)
*/
@ChangeUnit(id="birthday-changelog", order = "001", author = "fascinated7")
public class BirthdayProfileChangelog {
private final MongoTemplate mongoTemplate;
public BirthdayProfileChangelog(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
@Execution
public void changeSet() {
FindIterable<Document> guilds = mongoTemplate.getCollection("guilds").find();
guilds.forEach(guild -> {
Document profiles = guild.get("profiles", Document.class);
if (profiles == null) {
return;
}
Document birthdayProfile = profiles.get("birthday", Document.class);
birthdayProfile.remove("birthdays");
profiles.put("birthday", birthdayProfile);
guild.put("profiles", profiles);
mongoTemplate.getCollection("guilds").replaceOne(new Document("_id", guild.get("_id")), guild);
});
}
@RollbackExecution
public void rollback() {
// DO NOTHING
}
}

@ -0,0 +1,15 @@
package cc.fascinated.bat.changelog;
import io.mongock.runner.spring.base.events.SpringMigrationSuccessEvent;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component @Log4j2
public class MongockSuccessEventListener implements ApplicationListener<SpringMigrationSuccessEvent> {
@Override
public void onApplicationEvent(@NotNull SpringMigrationSuccessEvent event) {
log.info("Successfully ran Mongock migrations");
}
}

@ -57,7 +57,7 @@ public class SpotifyUtils {
Thread.sleep(250);
checks++;
} else {
log.info("Found new track {} in {} checks", track.getName(), checks);
log.info("Found new track \"{}\" in {} check{}", track.getName(), checks, checks == 1 ? "" : "s");
return currentlyPlaying;
}
}

@ -29,7 +29,7 @@ public class BirthdayFeature extends Feature {
/**
* Check birthdays every day at midnight
*/
@Scheduled(cron = "0 0 0 * * *")
@Scheduled(cron = "0 1 0 * * *")
private void checkBirthdays() {
for (BatGuild guild : guildService.getAllGuilds()) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class);

@ -0,0 +1,27 @@
package cc.fascinated.bat.features.birthday;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Date;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class UserBirthday {
/**
* The user's birthday
*/
private Date birthday;
/**
* If the birthday should be hidden
*/
private boolean hidden;
}

@ -19,5 +19,7 @@ public class BirthdayCommand extends BatCommand {
super.addSubCommand(context.getBean(RemoveSubCommand.class));
super.addSubCommand(context.getBean(ChannelSubCommand.class));
super.addSubCommand(context.getBean(MessageSubCommand.class));
super.addSubCommand(context.getBean(ViewSubCommand.class));
super.addSubCommand(context.getBean(PrivateSubCommand.class));
}
}

@ -0,0 +1,62 @@
package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("birthday:private.sub")
@CommandInfo(name = "private", description = "Changes whether your birthday is private or not")
public class PrivateSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public PrivateSubCommand(@NonNull GuildService guildService) {
this.guildService = guildService;
super.addOption(OptionType.BOOLEAN, "enabled", "Whether your birthday is private or not", true);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class);
OptionMapping enabledOption = interaction.getOption("enabled");
if (enabledOption == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide whether your birthday is private or not")
.build()).queue();
return;
}
boolean enabled = enabledOption.getAsBoolean();
UserBirthday birthday = profile.getBirthday(user.getId());
if (birthday == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You have not set your birthday yet")
.build()).queue();
return;
}
birthday.setHidden(enabled);
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Your birthday privacy settings have been updated\n\n**Private:** " + (enabled ? "Yes" : "No"))
.build()).queue();
}
}

@ -3,6 +3,7 @@ package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
@ -63,7 +64,7 @@ public class SetSubCommand extends BatSubCommand {
return;
}
profile.addBirthday(member.getId(), birthday);
profile.addBirthday(member.getId(), new UserBirthday(birthday, false));
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed()

@ -0,0 +1,76 @@
package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("birthday:view.sub")
@CommandInfo(name = "view", description = "Add your birthday to this guild")
public class ViewSubCommand extends BatSubCommand {
@Autowired
public ViewSubCommand(GuildService guildService) {
super.addOption(OptionType.USER, "user", "The user to view the birthday of", false);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class);
if (!profile.hasChannelSetup()) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Birthdays have not been enabled in this guild. Please ask an administrator to enable them.")
.build()).queue();
return;
}
OptionMapping birthdayOption = interaction.getOption("user");
if (birthdayOption == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a birthday")
.build()).queue();
return;
}
Member targetMember = birthdayOption.getAsMember();
if (targetMember == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a valid user")
.build()).queue();
return;
}
UserBirthday birthday = profile.getBirthday(targetMember.getId());
if (birthday == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("This user does not have a birthday set")
.build()).queue();
return;
}
if (birthday.isHidden() && !user.getId().equals(targetMember.getId())) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s has their birthday set to private".formatted(targetMember.getAsMention()))
.build()).queue();
return;
}
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s's birthday is <t:%s>".formatted(member.getAsMention(), birthday.getBirthday().toInstant().toEpochMilli()/1000))
.build()).queue();
}
}

@ -1,6 +1,7 @@
package cc.fascinated.bat.features.birthday.profile;
import cc.fascinated.bat.common.Profile;
import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.model.BatGuild;
import lombok.Getter;
import lombok.Setter;
@ -22,7 +23,7 @@ public class BirthdayProfile extends Profile {
/**
* The list of birthdays that are being tracked
*/
private Map<String, Date> birthdays;
private Map<String, UserBirthday> birthdays;
/**
* The channel ID of the birthday feed
@ -44,7 +45,7 @@ public class BirthdayProfile extends Profile {
* @param userId the id of the user to track
* @param birthday the birthday of the user
*/
public void addBirthday(String userId, Date birthday) {
public void addBirthday(String userId, UserBirthday birthday) {
if (birthdays == null) {
birthdays = new HashMap<>();
}
@ -69,7 +70,7 @@ public class BirthdayProfile extends Profile {
* @param userId the id of the user
* @return the birthday of the user
*/
public Date getBirthday(String userId) {
public UserBirthday getBirthday(String userId) {
if (birthdays == null) {
birthdays = new HashMap<>();
}
@ -92,13 +93,13 @@ public class BirthdayProfile extends Profile {
* @return the age of the user
*/
public int calculateAge(String userId) {
Date birthday = getBirthday(userId);
UserBirthday birthday = getBirthday(userId);
if (birthday == null) {
return 0; // or throw an exception
}
Calendar birthdayCalendar = Calendar.getInstance();
birthdayCalendar.setTime(birthday);
birthdayCalendar.setTime(birthday.getBirthday());
Calendar today = Calendar.getInstance();
@ -125,9 +126,9 @@ public class BirthdayProfile extends Profile {
List<String> toRemove = new ArrayList<>();
Guild discordGuild = guild.getDiscordGuild();
for (Map.Entry<String, Date> entry : birthdays.entrySet()) {
for (Map.Entry<String, UserBirthday> entry : birthdays.entrySet()) {
String userId = entry.getKey();
Date birthday = entry.getValue();
Date birthday = entry.getValue().getBirthday();
if (userId == null || birthday == null) { // this should never happen
continue;
@ -170,13 +171,10 @@ public class BirthdayProfile extends Profile {
int todayDay = today.get(Calendar.DAY_OF_MONTH);
int todayMonth = today.get(Calendar.MONTH); // Note: January is 0
Iterator<Map.Entry<String, Date>> iterator = birthdays.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Date> entry = iterator.next();
Date birthday = entry.getValue();
for (Map.Entry<String, UserBirthday> entry : birthdays.entrySet()) {
Date birthday = entry.getValue().getBirthday();
if (birthday == null) {
iterator.remove();
continue;
}

@ -9,13 +9,9 @@ import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.SpotifyService;
import com.fasterxml.jackson.datatype.jsr310.deser.JSR310DateTimeDeserializerBase;
import lombok.NonNull;
import lombok.SneakyThrows;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.components.ComponentInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;

@ -3,11 +3,9 @@ package cc.fascinated.bat.features.spotify.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.exception.BatException;
import cc.fascinated.bat.features.spotify.SpotifyFeature;
import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.SpotifyService;

@ -1,12 +1,8 @@
package cc.fascinated.bat.features.spotify.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.SpotifyUtils;
import cc.fascinated.bat.features.spotify.SpotifyFeature;
import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.SpotifyService;
@ -16,8 +12,6 @@ import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
import se.michaelthelin.spotify.model_objects.specification.Track;
/**
* @author Fascinated (fascinated7)

@ -1,12 +1,8 @@
package cc.fascinated.bat.features.spotify.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.SpotifyUtils;
import cc.fascinated.bat.features.spotify.SpotifyFeature;
import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.SpotifyService;
@ -16,8 +12,6 @@ import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
import se.michaelthelin.spotify.model_objects.specification.Track;
/**
* @author Fascinated (fascinated7)

@ -1,12 +1,8 @@
package cc.fascinated.bat.features.spotify.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.SpotifyUtils;
import cc.fascinated.bat.features.spotify.SpotifyFeature;
import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.SpotifyService;
@ -18,9 +14,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import se.michaelthelin.spotify.model_objects.IPlaylistItem;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
import se.michaelthelin.spotify.model_objects.specification.Track;
/**
* @author Fascinated (fascinated7)

@ -9,7 +9,6 @@ import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import net.jodah.expiringmap.ExpiringMap;
import org.apache.hc.core5.http.ParseException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import se.michaelthelin.spotify.SpotifyApi;
@ -18,7 +17,6 @@ import se.michaelthelin.spotify.exceptions.SpotifyWebApiException;
import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;