forked from Fascinated/Bat
Compare commits
33 Commits
4bf099d25e
...
f07e30d843
Author | SHA1 | Date | |
---|---|---|---|
f07e30d843 | |||
5f654f9ca6 | |||
4540bdef99 | |||
eda4eb5973 | |||
68a9a6dc48 | |||
ff23ea1d6c | |||
982c038b07 | |||
52223b5233 | |||
45755503a7 | |||
194a5d8119 | |||
120afee73b | |||
8b7340715c | |||
38bde93d16 | |||
a3ffaf1ab9 | |||
86c147f359 | |||
deb93e442c | |||
6eca92b4cf | |||
3f93df131d | |||
1028dca15a | |||
e03aef0ad5 | |||
8ce3a5d25c | |||
0b8caf3e25 | |||
317eaaec8a | |||
4f975ab07a | |||
1a69bce9dd | |||
37c69597be | |||
3146ed7d6d | |||
69281d113c | |||
c6289d1c8e | |||
3082265ec6 | |||
a057853cbd | |||
8b451c6ee5 | |||
727a4c9a6f |
21
pom.xml
21
pom.xml
@ -108,6 +108,22 @@
|
||||
<version>5.2.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis for caching -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Libraries -->
|
||||
<dependency>
|
||||
@ -143,6 +159,11 @@
|
||||
<artifactId>spotify-web-api-java</artifactId>
|
||||
<version>8.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>uk.co.conoregan</groupId>
|
||||
<artifactId>themoviedbapi</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<dependency>
|
||||
|
34
privacy-policy.txt
Normal file
34
privacy-policy.txt
Normal file
@ -0,0 +1,34 @@
|
||||
Privacy Policy
|
||||
|
||||
1. Introduction
|
||||
|
||||
This Privacy Policy explains how Bat ("we", "us", "our") collects, uses, and protects your information when you use our Discord bot (the "Service").
|
||||
2. Information We Collect
|
||||
|
||||
User Data: When you interact with our bot, we may collect your Discord user ID, server ID, and any messages or commands you send to the bot.
|
||||
Usage Data: We may collect data on how you interact with the bot, such as commands used and features accessed.
|
||||
|
||||
3. How We Use Your Information
|
||||
|
||||
Service Operation: To provide, maintain, and improve the Service.
|
||||
Communication: To respond to your inquiries and provide customer support.
|
||||
Analytics: To analyze usage trends and improve the Service.
|
||||
|
||||
4. Data Sharing
|
||||
|
||||
We do not sell or rent your information to third parties. We may share your information with third-party service providers who assist us in operating the Service, under confidentiality agreements.
|
||||
5. Data Security
|
||||
|
||||
We implement appropriate technical and organizational measures to protect your information from unauthorized access, loss, or misuse.
|
||||
6. Data Retention
|
||||
|
||||
We retain your information for as long as necessary to fulfill the purposes outlined in this Privacy Policy, unless a longer retention period is required or permitted by law.
|
||||
7. Your Rights
|
||||
|
||||
You have the right to access, correct, or delete your personal information. To exercise these rights, please contact us at bat@fascinated.cc.
|
||||
8. Changes to This Privacy Policy
|
||||
|
||||
We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on our https://discord.gg/yjj2U3ctEG.
|
||||
9. Contact Us
|
||||
|
||||
If you have any questions about this Privacy Policy, please contact us at bat@fascinated.cc.
|
@ -41,7 +41,6 @@ public class BatApplication {
|
||||
|
||||
// Start the app
|
||||
SpringApplication.run(BatApplication.class, args);
|
||||
|
||||
log.info("APP IS RUNNING IN %s MODE!!!!!!!!!".formatted(Config.isProduction() ? "PRODUCTION" : "DEVELOPMENT"));
|
||||
}
|
||||
|
||||
|
@ -6,12 +6,7 @@ package cc.fascinated.bat;
|
||||
public class Consts {
|
||||
public static final String INVITE_URL = "https://discord.com/oauth2/authorize?client_id=1254161119975833652&permissions=8&integration_type=0&scope=bot+applications.commands";
|
||||
public static final String SUPPORT_INVITE_URL = "https://discord.gg/invite/yjj2U3ctEG";
|
||||
public static String BOT_OWNER = "474221560031608833";
|
||||
public static String ADMIN_GUILD = "1203163422498361404";
|
||||
|
||||
static {
|
||||
if (System.getenv("ADMIN_GUILD") != null) {
|
||||
ADMIN_GUILD = System.getenv("ADMIN_GUILD");
|
||||
}
|
||||
}
|
||||
public static final String BOT_OWNER = "474221560031608833";
|
||||
public static final String PRIVACY_POLICY_URL = "https://git.fascinated.cc/Fascinated/Bat/raw/branch/master/privacy-policy.txt";
|
||||
public static final String TERMS_OF_SERVICE_URL = "https://git.fascinated.cc/Fascinated/Bat/raw/branch/master/terms-of-service.txt";
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ public enum Category {
|
||||
SERVER(Emoji.fromFormatted("U+1F5A5"), "Server", false),
|
||||
UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility", false),
|
||||
MUSIC(Emoji.fromFormatted("U+1F3B5"), "Music", false),
|
||||
MOVIES_TV(Emoji.fromFormatted("U+1F37F"), "Movies & TV", false),
|
||||
SNIPE(Emoji.fromFormatted("U+1F4A3"), "Snipe", false),
|
||||
LOGS(Emoji.fromFormatted("U+1F4D1"), "Logs", false),
|
||||
BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber", false),
|
||||
BOT_ADMIN(null, null, true);
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
package cc.fascinated.bat.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
public class EmbedDescriptionBuilder {
|
||||
/**
|
||||
* Where the description is stored
|
||||
*/
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
|
||||
public EmbedDescriptionBuilder(String title) {
|
||||
builder.append("**").append(title).append("**").append("\n");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public EmbedDescriptionBuilder appendLine(@NonNull String line, boolean arrow) {
|
||||
builder.append(arrow ? "➜ " : "").append(line).append("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public EmbedDescriptionBuilder appendSubtitle(@NonNull String title) {
|
||||
builder.append("**").append(title).append("**").append("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public EmbedDescriptionBuilder emptyLine() {
|
||||
builder.append("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String build() {
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
21
src/main/java/cc/fascinated/bat/common/EnumUtils.java
Normal file
21
src/main/java/cc/fascinated/bat/common/EnumUtils.java
Normal file
@ -0,0 +1,21 @@
|
||||
package cc.fascinated.bat.common;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
public class EnumUtils {
|
||||
/**
|
||||
* Gets the name of the enum
|
||||
*
|
||||
* @param e the enum
|
||||
* @return the name
|
||||
*/
|
||||
public static String getEnumName(Enum<?> e) {
|
||||
String[] split = e.name().split("_");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String s : split) {
|
||||
builder.append(s.substring(0, 1).toUpperCase()).append(s.substring(1).toLowerCase()).append(" ");
|
||||
}
|
||||
return builder.toString().trim();
|
||||
}
|
||||
}
|
39
src/main/java/cc/fascinated/bat/common/PasteUtils.java
Normal file
39
src/main/java/cc/fascinated/bat/common/PasteUtils.java
Normal file
@ -0,0 +1,39 @@
|
||||
package cc.fascinated.bat.common;
|
||||
|
||||
import cc.fascinated.bat.BatApplication;
|
||||
import cc.fascinated.bat.model.token.paste.PasteUploadToken;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Log4j2
|
||||
public class PasteUtils {
|
||||
private static final String PASTE_URL = "https://paste.fascinated.cc/";
|
||||
private static final String PASTE_UPLOAD_URL = PASTE_URL + "api/upload";
|
||||
private static final HttpClient httpClient = HttpClient.newHttpClient();
|
||||
|
||||
/**
|
||||
* Uploads a paste to the paste server
|
||||
*
|
||||
* @param content the content of the paste
|
||||
* @return the paste upload token
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static PasteUploadToken uploadPaste(String content) {
|
||||
HttpResponse<String> response = httpClient.send(HttpRequest.newBuilder()
|
||||
.uri(URI.create(PASTE_UPLOAD_URL))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(content))
|
||||
.build(), HttpResponse.BodyHandlers.ofString());
|
||||
PasteUploadToken paste = BatApplication.GSON.fromJson(response.body(), PasteUploadToken.class);
|
||||
paste.setUrl(PASTE_URL + paste.getKey());
|
||||
log.info("Created paste with key \"{}\" ({})", paste.getKey(), paste.getUrl());
|
||||
return paste;
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import cc.fascinated.bat.BatApplication;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import org.bson.Document;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -13,6 +15,7 @@ import java.util.Map;
|
||||
*/
|
||||
@Getter
|
||||
public abstract class ProfileHolder {
|
||||
private static final Logger log = LoggerFactory.getLogger(ProfileHolder.class);
|
||||
/**
|
||||
* The profiles for the holder
|
||||
*/
|
||||
|
@ -17,4 +17,21 @@ public class StringUtils {
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes meta characters in a string
|
||||
*
|
||||
* @param inputString the input string
|
||||
* @return the string with escaped meta characters
|
||||
*/
|
||||
public static String escapeMetaCharacters(String inputString){
|
||||
final String[] metaCharacters = {"\\","^","$","{","}","[","]","(",")",".","*","+","?","|","<",">","-","&","%"};
|
||||
|
||||
for (String metaCharacter : metaCharacters) {
|
||||
if (inputString.contains(metaCharacter)) {
|
||||
inputString = inputString.replace(metaCharacter, "\\" + metaCharacter);
|
||||
}
|
||||
}
|
||||
return inputString;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,37 @@
|
||||
package cc.fascinated.bat.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component @Getter
|
||||
public class Config {
|
||||
public static Config INSTANCE;
|
||||
|
||||
/**
|
||||
* Is the app running in a production environment?
|
||||
*/
|
||||
@Getter
|
||||
private static final boolean production;
|
||||
|
||||
/**
|
||||
* The ID of the admin guild
|
||||
*/
|
||||
private final String adminGuild;
|
||||
|
||||
static {
|
||||
// Are we running on production?
|
||||
String appEnv = System.getenv("APP_ENV");
|
||||
production = appEnv != null && (appEnv.equals("production"));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public Config(@Value("${bat.admin-guild}") String adminGuild) {
|
||||
INSTANCE = this;
|
||||
this.adminGuild = adminGuild;
|
||||
}
|
||||
}
|
||||
|
73
src/main/java/cc/fascinated/bat/config/RedisConfig.java
Normal file
73
src/main/java/cc/fascinated/bat/config/RedisConfig.java
Normal file
@ -0,0 +1,73 @@
|
||||
package cc.fascinated.bat.config;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Configuration
|
||||
@Log4j2(topic = "Redis")
|
||||
public class RedisConfig {
|
||||
/**
|
||||
* The Redis server host.
|
||||
*/
|
||||
@Value("${spring.data.redis.host}")
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* The Redis server port.
|
||||
*/
|
||||
@Value("${spring.data.redis.port}")
|
||||
private int port;
|
||||
|
||||
/**
|
||||
* The Redis database index.
|
||||
*/
|
||||
@Value("${spring.data.redis.database}")
|
||||
private int database;
|
||||
|
||||
/**
|
||||
* The optional Redis password.
|
||||
*/
|
||||
@Value("${spring.data.redis.auth}")
|
||||
private String auth;
|
||||
|
||||
/**
|
||||
* Build the config to use for Redis.
|
||||
*
|
||||
* @return the config
|
||||
* @see RedisTemplate for config
|
||||
*/
|
||||
@Bean @NonNull
|
||||
public RedisTemplate<String, Object> redisTemplate() {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(jedisConnectionFactory());
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the connection factory to use
|
||||
* when making connections to Redis.
|
||||
*
|
||||
* @return the built factory
|
||||
* @see JedisConnectionFactory for factory
|
||||
*/
|
||||
@Bean @NonNull
|
||||
public JedisConnectionFactory jedisConnectionFactory() {
|
||||
log.info("Connecting to Redis at {}:{}/{}", host, port, database);
|
||||
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
|
||||
config.setDatabase(database);
|
||||
if (!auth.trim().isEmpty()) { // Auth with our provided password
|
||||
log.info("Using auth...");
|
||||
config.setPassword(auth);
|
||||
}
|
||||
return new JedisConnectionFactory(config);
|
||||
}
|
||||
}
|
@ -2,18 +2,34 @@ package cc.fascinated.bat.event;
|
||||
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import cc.fascinated.bat.model.DiscordMessage;
|
||||
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberLeaderboardToken;
|
||||
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberPlayerScoreToken;
|
||||
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
|
||||
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.channel.update.GenericChannelUpdateEvent;
|
||||
import net.dv8tion.jda.api.events.guild.GuildBanEvent;
|
||||
import net.dv8tion.jda.api.events.guild.GuildUnbanEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleAddEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleRemoveEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateAvatarEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateNameEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
@ -45,7 +61,7 @@ public interface EventListener {
|
||||
* @param guild the guild the user left
|
||||
* @param user the user that left the guild
|
||||
*/
|
||||
default void onGuildMemberLeave(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberRemoveEvent event) {
|
||||
default void onGuildMemberLeave(@NonNull BatGuild guild, BatUser user, @NonNull GuildMemberRemoveEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,6 +73,25 @@ public interface EventListener {
|
||||
default void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user updates a message
|
||||
*
|
||||
* @param guild the guild that the message was updated in
|
||||
* @param user the user that updated the message
|
||||
*/
|
||||
default void onGuildMessageEdit(@NonNull BatGuild guild, @NonNull BatUser user, DiscordMessage oldMessage,
|
||||
@NonNull DiscordMessage newMessage, @NonNull MessageUpdateEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user deletes a message
|
||||
*
|
||||
* @param guild the guild that the message was deleted in
|
||||
* @param user the user that deleted the message
|
||||
*/
|
||||
default void onGuildMessageDelete(@NonNull BatGuild guild, BatUser user, DiscordMessage message, @NonNull MessageDeleteEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user selects a string
|
||||
*
|
||||
@ -105,6 +140,98 @@ public interface EventListener {
|
||||
default void onGuildMemberUpdateNickname(@NonNull BatGuild guild, @NonNull BatUser user, String oldName, String newName, @NonNull GuildMemberUpdateNicknameEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user gets roles added to them
|
||||
*
|
||||
* @param guild the guild that the user added the role in
|
||||
* @param user the user that added the role
|
||||
* @param rolesAdded the roles that were added
|
||||
*/
|
||||
default void onGuildMemberRoleAdd(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull List<Role> rolesAdded, @NonNull GuildMemberRoleAddEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user gets roles removed from them
|
||||
*
|
||||
* @param guild the guild that the user removed the role in
|
||||
* @param user the user that removed the role
|
||||
* @param rolesAdded the roles that were removed
|
||||
*/
|
||||
default void onGuildMemberRoleRemove(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull List<Role> rolesAdded, @NonNull GuildMemberRoleRemoveEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a channel is created
|
||||
*
|
||||
* @param guild the guild that the channel was created in
|
||||
*/
|
||||
default void onChannelCreate(@NonNull BatGuild guild, @NonNull ChannelCreateEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a channel is deleted
|
||||
*
|
||||
* @param guild the guild that the channel was deleted in
|
||||
*/
|
||||
default void onChannelDelete(@NonNull BatGuild guild, @NonNull ChannelDeleteEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user is banned from a guild
|
||||
*
|
||||
* @param guild the guild that the user was banned from
|
||||
* @param user the user that was banned
|
||||
*/
|
||||
default void onGuildMemberBan(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildBanEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user is unbanned from a guild
|
||||
*
|
||||
* @param guild the guild that the user was unbanned from
|
||||
* @param user the user that was unbanned
|
||||
*/
|
||||
default void onGuildMemberUnban(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildUnbanEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user gets timed out in a guild (gets muted)
|
||||
*
|
||||
* @param guild the guild that the user timed out in
|
||||
* @param user the user that timed out
|
||||
*/
|
||||
default void onGuildMemberTimeout(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberUpdateTimeOutEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a channels state is updated
|
||||
*
|
||||
* @param guild the guild that the channel was updated in
|
||||
* @param event the event that was fired
|
||||
*/
|
||||
default void onGenericChannelUpdate(@NonNull BatGuild guild, @NonNull GenericChannelUpdateEvent<?> event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user updates their username
|
||||
*
|
||||
* @param user the user that updated their name
|
||||
* @param oldName the old username
|
||||
* @param newName the new username
|
||||
*/
|
||||
default void onUserUpdateName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateNameEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user updates their avatar
|
||||
*
|
||||
* @param user the user that updated their avatar
|
||||
* @param oldAvatarUrl the old avatar url
|
||||
* @param newAvatarUrl the new avatar url
|
||||
*/
|
||||
default void onUserUpdateAvatar(@NonNull BatUser user, String oldAvatarUrl, String newAvatarUrl, @NonNull UserUpdateAvatarEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Spring is shutting down
|
||||
*/
|
||||
|
@ -2,6 +2,7 @@ package cc.fascinated.bat.features.base.commands.general;
|
||||
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.common.NumberFormatter;
|
||||
import cc.fascinated.bat.common.TimeUtils;
|
||||
@ -42,15 +43,16 @@ public class BotStatsCommand extends BatCommand {
|
||||
JDA jda = DiscordService.JDA;
|
||||
|
||||
event.replyEmbeds(EmbedUtils.genericEmbed().setDescription(
|
||||
"**Bot Statistics**\n" +
|
||||
"➜ Guilds: **%s**\n".formatted(NumberFormatter.format(jda.getGuilds().size())) +
|
||||
"➜ Users: **%s**\n".formatted(NumberFormatter.format(jda.getUsers().size())) +
|
||||
"➜ Gateway Ping: **%sms**\n".formatted(jda.getGatewayPing()) +
|
||||
"\n" +
|
||||
"**Bat Statistics**\n" +
|
||||
"➜ Uptime: **%s**\n".formatted(TimeUtils.format(bean.getUptime())) +
|
||||
"➜ Cached Guilds: **%s**\n".formatted(NumberFormatter.format(guildService.getGuilds().size())) +
|
||||
"➜ Cached Users: **%s**".formatted(NumberFormatter.format(userService.getUsers().size()))
|
||||
new EmbedDescriptionBuilder("Bat Statistics")
|
||||
.appendLine("Guilds: **%s**".formatted(NumberFormatter.format(jda.getGuilds().size())), true)
|
||||
.appendLine("Users: **%s**".formatted(NumberFormatter.format(jda.getUsers().size())), true)
|
||||
.appendLine("Gateway Ping: **%sms**".formatted(jda.getGatewayPing()), true)
|
||||
.emptyLine()
|
||||
.appendSubtitle("Bot Statistics")
|
||||
.appendLine("Uptime: **%s**".formatted(TimeUtils.format(bean.getUptime())), true)
|
||||
.appendLine("Cached Guilds: **%s**".formatted(NumberFormatter.format(guildService.getGuilds().size())), true)
|
||||
.appendLine("Cached Users: **%s**".formatted(NumberFormatter.format(userService.getUsers().size())), true)
|
||||
.build()
|
||||
).build()).queue();
|
||||
}
|
||||
}
|
||||
|
@ -63,30 +63,30 @@ public class HelpCommand extends BatCommand implements EventListener {
|
||||
return;
|
||||
}
|
||||
|
||||
String commands = "";
|
||||
StringBuilder commands = new StringBuilder();
|
||||
List<BatCommand> categoryCommands = commandService.getCommandsByCategory(category, true);
|
||||
if (categoryCommands.isEmpty()) {
|
||||
commands = "No commands available in this category.";
|
||||
commands = new StringBuilder("No commands available in this category.");
|
||||
} else {
|
||||
for (BatCommand command : categoryCommands) {
|
||||
if (!command.getSubCommands().isEmpty()) {
|
||||
for (Map.Entry<String, BatSubCommand> entry : command.getSubCommands().entrySet()) {
|
||||
BatSubCommand subCommand = entry.getValue();
|
||||
SubcommandData commandData = subCommand.getCommandData();
|
||||
commands += "</%s %s:%s> - %s\n".formatted(
|
||||
commands.append("</%s %s:%s> - %s\n".formatted(
|
||||
command.getCommandInfo().name(),
|
||||
commandData.getName(),
|
||||
subCommand.getCommandSnowflake(),
|
||||
commandData.getDescription()
|
||||
);
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
commands += "</%s:%s> - %s\n".formatted(
|
||||
commands.append("</%s:%s> - %s\n".formatted(
|
||||
command.getCommandInfo().name(),
|
||||
command.getCommandSnowflake(),
|
||||
command.getCommandInfo().description()
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ public class HelpCommand extends BatCommand implements EventListener {
|
||||
categoryCommands.size() == 1 ? "" : "s",
|
||||
subCommands,
|
||||
subCommands == 1 ? "" : "s",
|
||||
commands
|
||||
commands.toString()
|
||||
)).build()).queue();
|
||||
}
|
||||
|
||||
@ -108,18 +108,27 @@ public class HelpCommand extends BatCommand implements EventListener {
|
||||
* @return The home embed
|
||||
*/
|
||||
private MessageEmbed createHomeEmbed() {
|
||||
String categories = "";
|
||||
StringBuilder categories = new StringBuilder();
|
||||
for (Category category : Category.getCategories()) {
|
||||
long commandCount = commandService.getCommandsByCategory(category, true).size();
|
||||
categories += "➜ %s - **%s Command%s**\n".formatted(
|
||||
categories.append("➜ %s - **%s Command%s**\n".formatted(
|
||||
category.getName(),
|
||||
commandCount,
|
||||
commandCount == 1 ? "" : "s"
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
return EmbedUtils.genericEmbed()
|
||||
.setDescription("Here are the available command categories: \n\n" + categories)
|
||||
.setDescription("""
|
||||
**Welcome to the Bat Help Menu!**
|
||||
|
||||
%s
|
||||
*View our [TOS](%s) and [Privacy Policy](%s) for more information.*
|
||||
""".formatted(
|
||||
categories.toString(),
|
||||
Consts.TERMS_OF_SERVICE_URL,
|
||||
Consts.PRIVACY_POLICY_URL
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package cc.fascinated.bat.features.base.commands.general;
|
||||
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
@ -18,18 +19,19 @@ import org.springframework.stereotype.Component;
|
||||
@CommandInfo(name = "vote", description = "Vote for the bot", guildOnly = false)
|
||||
public class VoteCommand extends BatCommand {
|
||||
private static final String[] VOTE_LINKS = new String[]{
|
||||
"https://top.gg/bot/1254161119975833652/vote"
|
||||
"https://top.gg/bot/1254161119975833652/vote",
|
||||
"https://discordbotlist.com/bots/bat/upvote"
|
||||
};
|
||||
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("You can vote for the bot by clicking the following links:\n\n");
|
||||
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Vote Links");
|
||||
description.appendLine("Vote for the bot on the following websites to support us!", true);
|
||||
for (String link : VOTE_LINKS) {
|
||||
builder.append("%s\n".formatted(link));
|
||||
description.appendLine(link, true);
|
||||
}
|
||||
event.replyEmbeds(EmbedUtils.genericEmbed()
|
||||
.setDescription(builder.toString())
|
||||
.setDescription(description.build())
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import cc.fascinated.bat.common.NumberFormatter;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
@ -26,7 +26,7 @@ public class PremiumCommand extends BatCommand {
|
||||
EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information");
|
||||
if (premium.hasPremium()) {
|
||||
embed.addField("Premium", premium.hasPremium() ? "Yes" : "No", true);
|
||||
embed.addField("Started", "<t:%d>".formatted(premium.getActivatedAt().toInstant().toEpochMilli() / 1000), true);
|
||||
embed.addField("Started", "<t:%d>".formatted(premium.getActivatedAt().toInstant().getEpochSecond()), true);
|
||||
embed.addField("Expires", premium.isInfinite() ? "Never" : "<t:%d>"
|
||||
.formatted(premium.getExpiresAt().toInstant().toEpochMilli() / 1000), true);
|
||||
} else {
|
||||
|
@ -0,0 +1,34 @@
|
||||
package cc.fascinated.bat.features.logging;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@AllArgsConstructor @Getter
|
||||
public enum LogCategory {
|
||||
MESSAGE("Message"),
|
||||
MEMBER("Member"),
|
||||
CHANNEL("Channel");
|
||||
|
||||
/**
|
||||
* The name of the log category
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Gets the log category by the name
|
||||
*
|
||||
* @param name - the name
|
||||
* @return the log category, or null if it doesn't exist
|
||||
*/
|
||||
public static LogCategory getLogCategory(String name) {
|
||||
for (LogCategory logCategory : values()) {
|
||||
if (logCategory.getName().equalsIgnoreCase(name)) {
|
||||
return logCategory;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package cc.fascinated.bat.features.logging;
|
||||
|
||||
import cc.fascinated.bat.command.Category;
|
||||
import cc.fascinated.bat.common.PasteUtils;
|
||||
import cc.fascinated.bat.features.Feature;
|
||||
import cc.fascinated.bat.features.base.profile.FeatureProfile;
|
||||
import cc.fascinated.bat.features.logging.command.LogsCommand;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.service.CommandService;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
public class LogFeature extends Feature {
|
||||
@Autowired
|
||||
public LogFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
|
||||
super("Logging", false, Category.LOGS);
|
||||
|
||||
super.registerCommand(commandService, context.getBean(LogsCommand.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a log to the log channel
|
||||
*
|
||||
* @param guild the guild to send the log in
|
||||
* @param type the type of log
|
||||
* @param embed the embed to send
|
||||
*/
|
||||
public void sendLog(BatGuild guild, LogType type, MessageEmbed embed) {
|
||||
FeatureProfile featureProfile = guild.getFeatureProfile();
|
||||
if (featureProfile.isFeatureDisabled(this)) { // The feature is disabled
|
||||
return;
|
||||
}
|
||||
LogProfile logProfile = guild.getLogProfile();
|
||||
if (!logProfile.hasLogChannel(type)) { // The guild has no log channel for this type
|
||||
return;
|
||||
}
|
||||
TextChannel logChannel = logProfile.getLogChannel(type);
|
||||
if (logChannel == null) { // The log channel has been removed
|
||||
return;
|
||||
}
|
||||
logChannel.sendMessageEmbeds(embed).queue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the content to be sent in the log
|
||||
*
|
||||
* @param content the content to format
|
||||
* @return the formatted content
|
||||
*/
|
||||
public String formatContent(String content) {
|
||||
return content.length() > 512 ? PasteUtils.uploadPaste(content).getUrl() : "\n```\n%s\n```".formatted(content);
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package cc.fascinated.bat.features.logging;
|
||||
|
||||
import cc.fascinated.bat.common.Serializable;
|
||||
import cc.fascinated.bat.service.DiscordService;
|
||||
import com.google.gson.Gson;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
public class LogProfile extends Serializable {
|
||||
/**
|
||||
* The log channels for this profile
|
||||
*/
|
||||
private final Map<LogType, TextChannel> logChannels = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Checks if the log channel for the specified log type exists
|
||||
*
|
||||
* @param logType - the log type
|
||||
* @return true if it exists, false otherwise
|
||||
*/
|
||||
public boolean hasLogChannel(LogType logType) {
|
||||
return this.logChannels.containsKey(logType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the log channel for the specified log type
|
||||
*
|
||||
* @param logType - the log type
|
||||
* @return the log channel, or null if it doesn't exist
|
||||
*/
|
||||
public TextChannel getLogChannel(LogType logType) {
|
||||
TextChannel textChannel = this.logChannels.get(logType);
|
||||
if (textChannel == null) {
|
||||
return null;
|
||||
}
|
||||
// Ensure the channel exists
|
||||
if (DiscordService.JDA.getTextChannelById(textChannel.getId()) == null) {
|
||||
this.logChannels.remove(logType);
|
||||
return null;
|
||||
}
|
||||
return textChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the log channel for the specified log type
|
||||
*
|
||||
* @param logType - the log type
|
||||
* @param channel - the channel
|
||||
*/
|
||||
public void setLogChannel(LogType logType, TextChannel channel) {
|
||||
this.logChannels.put(logType, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the log channel for the specified log type
|
||||
*
|
||||
* @param logType - the log type
|
||||
*/
|
||||
public void removeLogChannel(LogType logType) {
|
||||
this.logChannels.remove(logType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(Document document, Gson gson) {
|
||||
JDA jda = DiscordService.JDA;
|
||||
for (LogType logType : LogType.values()) {
|
||||
if (document.containsKey(logType.name())) {
|
||||
TextChannel channel = jda.getTextChannelById(document.getString(logType.name()));
|
||||
if (channel == null) {
|
||||
return;
|
||||
}
|
||||
this.logChannels.put(logType, channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document serialize(Gson gson) {
|
||||
Document document = new Document();
|
||||
for (Map.Entry<LogType, TextChannel> entry : this.logChannels.entrySet()) {
|
||||
document.append(entry.getKey().name(), entry.getValue().getId());
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.logChannels.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package cc.fascinated.bat.features.logging;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@AllArgsConstructor @Getter
|
||||
public enum LogType {
|
||||
/**
|
||||
* Message Events
|
||||
*/
|
||||
MESSAGE_DELETE(LogCategory.MESSAGE, "Message Delete"),
|
||||
MESSAGE_EDIT(LogCategory.MESSAGE,"Message Edit"),
|
||||
|
||||
/**
|
||||
* Member Events
|
||||
*/
|
||||
MEMBER_JOIN(LogCategory.MEMBER, "Member Join"),
|
||||
MEMBER_LEAVE(LogCategory.MEMBER, "Member Leave"),
|
||||
MEMBER_NICKNAME_UPDATE(LogCategory.MEMBER, "Member Nickname Update"),
|
||||
MEMBER_GLOBAL_NAME_UPDATE(LogCategory.MEMBER, "Member Global Name Update"),
|
||||
MEMBER_USERNAME_UPDATE(LogCategory.MEMBER, "Member Username Update"),
|
||||
MEMBER_AVATAR_UPDATE(LogCategory.MEMBER, "Member Avatar Update"),
|
||||
MEMBER_ROLE_UPDATE(LogCategory.MEMBER, "Member Role Update"),
|
||||
MEMBER_BAN(LogCategory.MEMBER, "Member Ban"),
|
||||
MEMBER_UNBAN(LogCategory.MEMBER, "Member Unban"),
|
||||
MEMBER_TIMEOUT(LogCategory.MEMBER, "Member Timeout"),
|
||||
|
||||
/**
|
||||
* Channel Events
|
||||
*/
|
||||
CHANNEL_CREATE(LogCategory.CHANNEL, "Channel Create"),
|
||||
CHANNEL_DELETE(LogCategory.CHANNEL, "Channel Delete");
|
||||
|
||||
/**
|
||||
* The category of the log type
|
||||
*/
|
||||
private final LogCategory category;
|
||||
|
||||
/**
|
||||
* The name of the log type
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Gets the log type by the name
|
||||
*
|
||||
* @param name - the name
|
||||
* @return the log type, or null if it doesn't exist
|
||||
*/
|
||||
public static LogType getLogType(String name) {
|
||||
for (LogType logType : values()) {
|
||||
if (logType.getName().equalsIgnoreCase(name)) {
|
||||
return logType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the log types by the category
|
||||
*
|
||||
* @param category - the category
|
||||
* @return the log types
|
||||
*/
|
||||
public static List<LogType> getLogTypesByCategory(String category) {
|
||||
List<LogType> logTypes = new ArrayList<>();
|
||||
for (LogType logType : values()) {
|
||||
if (logType.getCategory().getName().equalsIgnoreCase(category)) {
|
||||
logTypes.add(logType);
|
||||
}
|
||||
}
|
||||
return logTypes;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package cc.fascinated.bat.features.logging.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatSubCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.features.logging.LogCategory;
|
||||
import cc.fascinated.bat.features.logging.LogProfile;
|
||||
import cc.fascinated.bat.features.logging.LogType;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component("logs:list.sub")
|
||||
@CommandInfo(name = "list", description = "See all the log types and their channels")
|
||||
public class ListSubCommand extends BatSubCommand {
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
LogProfile profile = guild.getLogProfile();
|
||||
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Log Channels");
|
||||
description.appendLine("""
|
||||
Set the log channel for:
|
||||
- A specific event, use `/logs set <event> <channel>`
|
||||
- A specific category by using `/logs set <category> <channel>`
|
||||
- All log types by using `/logs set all <channel>`
|
||||
To remove a log channel, it's the same as setting it,
|
||||
but with `/logs remove` instead of `/logs set`""", false);
|
||||
description.emptyLine();
|
||||
for (int i = 0; i < LogCategory.values().length; i++) {
|
||||
LogCategory category = LogCategory.values()[i];
|
||||
if (i != 0) {
|
||||
description.emptyLine();
|
||||
}
|
||||
description.appendLine("**__%s__**".formatted(category.getName()), false);
|
||||
for (LogType logType : LogType.values()) {
|
||||
if (logType.getCategory() == category) {
|
||||
TextChannel logChannel = profile.getLogChannel(logType);
|
||||
description.appendLine("%s: %s".formatted(logType.getName(), logChannel == null ? "Not Set" : logChannel.getAsMention()), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
event.replyEmbeds(EmbedUtils.genericEmbed().setDescription(description.build()).build()).queue();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package cc.fascinated.bat.features.logging.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
@CommandInfo(name = "logs", description = "Edit logging settings", requiredPermissions = Permission.MANAGE_SERVER)
|
||||
public class LogsCommand extends BatCommand {
|
||||
@Autowired
|
||||
public LogsCommand(@NonNull ApplicationContext context) {
|
||||
super.addSubCommand(context.getBean(SetSubCommand.class));
|
||||
super.addSubCommand(context.getBean(RemoveSubCommand.class));
|
||||
super.addSubCommand(context.getBean(ListSubCommand.class));
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package cc.fascinated.bat.features.logging.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatSubCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.features.logging.LogCategory;
|
||||
import cc.fascinated.bat.features.logging.LogProfile;
|
||||
import cc.fascinated.bat.features.logging.LogType;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component("logs:remove.sub")
|
||||
@CommandInfo(name = "remove", description = "Remove the channel for a log type")
|
||||
public class RemoveSubCommand extends BatSubCommand {
|
||||
public RemoveSubCommand() {
|
||||
super.addOption(OptionType.STRING, "type", "The type of log to remove", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
OptionMapping typeOption = event.getOption("type");
|
||||
if (typeOption == null) {
|
||||
return;
|
||||
}
|
||||
String type = typeOption.getAsString();
|
||||
LogProfile profile = guild.getLogProfile();
|
||||
|
||||
// Remove the log channel for all log types
|
||||
if (type.equalsIgnoreCase("all")) {
|
||||
for (LogType logType : LogType.values()) {
|
||||
profile.removeLogChannel(logType);
|
||||
}
|
||||
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription("Successfully removed the log channel for all log types")
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the log channel for a specific log category
|
||||
LogCategory logCategory = LogCategory.getLogCategory(type);
|
||||
if (logCategory != null) {
|
||||
List<LogType> category = LogType.getLogTypesByCategory(type);
|
||||
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Log Channel");
|
||||
description.appendLine("Successfully removed the log channel for the `%s` category"
|
||||
.formatted(logCategory.getName()), false);
|
||||
description.emptyLine();
|
||||
|
||||
int removed = 0;
|
||||
for (LogType logType : category) {
|
||||
if (!profile.hasLogChannel(logType)) {
|
||||
continue;
|
||||
}
|
||||
description.appendLine(logType.getName(), true);
|
||||
profile.removeLogChannel(logType);
|
||||
removed++;
|
||||
}
|
||||
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription(removed == 0 ? "No log channels were removed for the `%s` category".formatted(logCategory.getName()) : description.build())
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the log channel for a specific log type
|
||||
LogType logType = LogType.getLogType(type);
|
||||
if (logType == null) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("Invalid log type")
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!profile.hasLogChannel(logType)) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("The log channel for `%s` is not set".formatted(logType.getName()))
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
profile.removeLogChannel(logType);
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription("Successfully removed the log channel for `%s`".formatted(logType.getName()))
|
||||
.build()).queue();
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package cc.fascinated.bat.features.logging.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatSubCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.features.logging.LogCategory;
|
||||
import cc.fascinated.bat.features.logging.LogProfile;
|
||||
import cc.fascinated.bat.features.logging.LogType;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component("logs:set.sub")
|
||||
@CommandInfo(name = "set", description = "Set the channel for a log type")
|
||||
public class SetSubCommand extends BatSubCommand {
|
||||
public SetSubCommand() {
|
||||
super.addOption(OptionType.STRING, "type", "The type of log to set", true);
|
||||
super.addOption(OptionType.CHANNEL, "channel", "The channel to set the log to", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
OptionMapping typeOption = event.getOption("type");
|
||||
if (typeOption == null) {
|
||||
return;
|
||||
}
|
||||
OptionMapping channelOption = event.getOption("channel");
|
||||
if (channelOption == null) {
|
||||
return;
|
||||
}
|
||||
String type = typeOption.getAsString();
|
||||
TextChannel targetChannel = channelOption.getAsChannel().asTextChannel();
|
||||
LogProfile profile = guild.getLogProfile();
|
||||
|
||||
// Set the log channel for all log types
|
||||
if (type.equalsIgnoreCase("all")) {
|
||||
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Log Channel");
|
||||
description.appendLine("Successfully set the log channel for all log types to %s".formatted(targetChannel.getAsMention()), false);
|
||||
description.emptyLine();
|
||||
for (int i = 0; i < LogCategory.values().length; i++) {
|
||||
LogCategory category = LogCategory.values()[i];
|
||||
if (i != 0) {
|
||||
description.emptyLine();
|
||||
}
|
||||
description.appendLine("**__%s__**".formatted(category.getName()), false);
|
||||
for (LogType logType : LogType.getLogTypesByCategory(category.getName())) {
|
||||
description.appendLine(logType.getName(), true);
|
||||
profile.setLogChannel(logType, targetChannel);
|
||||
}
|
||||
}
|
||||
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription(description.build())
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the log channel for a specific log category
|
||||
LogCategory logCategory = LogCategory.getLogCategory(type);
|
||||
if (logCategory != null) {
|
||||
List<LogType> category = LogType.getLogTypesByCategory(type);
|
||||
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Log Channel");
|
||||
description.appendLine("Successfully set the log channel for the `%s` category to %s"
|
||||
.formatted(logCategory.getName(), targetChannel.getAsMention()), false);
|
||||
description.emptyLine();
|
||||
for (LogType logType : category) {
|
||||
description.appendLine(logType.getName(), true);
|
||||
profile.setLogChannel(logType, targetChannel);
|
||||
}
|
||||
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription(description.build())
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the log channel for a specific log type
|
||||
LogType logType = LogType.getLogType(type);
|
||||
if (logType == null) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("Invalid log type")
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
profile.setLogChannel(logType, targetChannel);
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription("Successfully set the log channel for `%s` to %s".formatted(logType.getName(), targetChannel.getAsMention()))
|
||||
.build()).queue();
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package cc.fascinated.bat.features.logging.listeners;
|
||||
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.common.EnumUtils;
|
||||
import cc.fascinated.bat.event.EventListener;
|
||||
import cc.fascinated.bat.features.logging.LogFeature;
|
||||
import cc.fascinated.bat.features.logging.LogType;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.unions.ChannelUnion;
|
||||
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
|
||||
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
public class ChannelListener implements EventListener {
|
||||
private final LogFeature logFeature;
|
||||
|
||||
@Autowired
|
||||
public ChannelListener(@NonNull ApplicationContext context) {
|
||||
this.logFeature = context.getBean(LogFeature.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelCreate(@NonNull BatGuild guild, @NonNull ChannelCreateEvent event) {
|
||||
logFeature.sendLog(guild, LogType.CHANNEL_CREATE, EmbedUtils.successEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("%s Channel Created".formatted(EnumUtils.getEnumName(event.getChannel().getType())))
|
||||
.appendLine("Channel: %s".formatted(event.getChannel().getAsMention()), true)
|
||||
.appendLine("Name: %s".formatted(event.getChannel().getName()), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelDelete(@NonNull BatGuild guild, @NonNull ChannelDeleteEvent event) {
|
||||
ChannelUnion channel = event.getChannel();
|
||||
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("%s Channel Deleted".formatted(EnumUtils.getEnumName(channel.getType())))
|
||||
.appendLine("Name: #%s".formatted(channel.getName()), true);
|
||||
if (channel.getType().isMessage()) {
|
||||
TextChannel textChannel = channel.asTextChannel();
|
||||
description.appendLine("Topic: %s".formatted(textChannel.getTopic()), true);
|
||||
}
|
||||
logFeature.sendLog(guild, LogType.CHANNEL_DELETE, EmbedUtils.errorEmbed().setDescription(description.build()).build());
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package cc.fascinated.bat.features.logging.listeners;
|
||||
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.event.EventListener;
|
||||
import cc.fascinated.bat.features.logging.LogFeature;
|
||||
import cc.fascinated.bat.features.logging.LogType;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import cc.fascinated.bat.service.DiscordService;
|
||||
import cc.fascinated.bat.service.GuildService;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.events.guild.GuildBanEvent;
|
||||
import net.dv8tion.jda.api.events.guild.GuildUnbanEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleAddEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleRemoveEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateAvatarEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateNameEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
public class MemberListener implements EventListener {
|
||||
private final LogFeature logFeature;
|
||||
private final GuildService guildService;
|
||||
|
||||
@Autowired
|
||||
public MemberListener(@NonNull ApplicationContext context, @NonNull GuildService guildService) {
|
||||
this.logFeature = context.getBean(LogFeature.class);
|
||||
this.guildService = guildService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberJoin(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberJoinEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
|
||||
logFeature.sendLog(guild, LogType.MEMBER_JOIN, EmbedUtils.successEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Joined")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Username: %s".formatted(user.getDiscordUser().getName()), true)
|
||||
.appendLine("Account Age: <t:%s:R>".formatted(user.getDiscordUser().getTimeCreated().toEpochSecond()), true)
|
||||
.build())
|
||||
.setThumbnail(user.getDiscordUser().getEffectiveAvatarUrl())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberLeave(@NonNull BatGuild guild, BatUser user, @NonNull GuildMemberRemoveEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
|
||||
logFeature.sendLog(guild, LogType.MEMBER_LEAVE, EmbedUtils.errorEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Left")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Username: %s".formatted(user.getDiscordUser().getName()), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberUpdateNickname(@NonNull BatGuild guild, @NonNull BatUser user, String oldName, String newName, @NonNull GuildMemberUpdateNicknameEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
|
||||
logFeature.sendLog(guild, LogType.MEMBER_NICKNAME_UPDATE, EmbedUtils.genericEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Nickname Updated")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Old Nickname: `%s`".formatted(oldName == null ? user.getName() : oldName), true)
|
||||
.appendLine("New Nickname: `%s`".formatted(newName == null ? "Removed Nickname" : newName), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserUpdateGlobalName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateGlobalNameEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
for (Guild guild : DiscordService.JDA.getGuilds()) {
|
||||
BatGuild batGuild = guildService.getGuild(guild.getId());
|
||||
if (batGuild == null) continue;
|
||||
|
||||
logFeature.sendLog(batGuild, LogType.MEMBER_GLOBAL_NAME_UPDATE, EmbedUtils.genericEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Name Updated")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Old Name: `%s`".formatted(oldName == null ? user.getName() : oldName), true)
|
||||
.appendLine("New Name: `%s`".formatted(newName == null ? "Removed Name" : newName), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserUpdateName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateNameEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
for (Guild guild : DiscordService.JDA.getGuilds()) {
|
||||
BatGuild batGuild = guildService.getGuild(guild.getId());
|
||||
if (batGuild == null) continue;
|
||||
|
||||
logFeature.sendLog(batGuild, LogType.MEMBER_USERNAME_UPDATE, EmbedUtils.genericEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Username Updated")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Old Username: `%s`".formatted(oldName), true)
|
||||
.appendLine("New Username: `%s`".formatted(newName), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserUpdateAvatar(@NonNull BatUser user, String oldAvatarUrl, String newAvatarUrl, @NonNull UserUpdateAvatarEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
for (Guild guild : DiscordService.JDA.getGuilds()) {
|
||||
BatGuild batGuild = guildService.getGuild(guild.getId());
|
||||
if (batGuild == null) continue;
|
||||
|
||||
logFeature.sendLog(batGuild, LogType.MEMBER_USERNAME_UPDATE, EmbedUtils.genericEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Avatar Updated")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Old Avatar: [avatar](%s)".formatted(oldAvatarUrl), true)
|
||||
.appendLine("New Avatar: [avatar](%s)".formatted(newAvatarUrl), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberRoleAdd(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull List<Role> rolesAdded, @NonNull GuildMemberRoleAddEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
|
||||
StringBuilder roles = new StringBuilder();
|
||||
for (Role role : rolesAdded) {
|
||||
roles.append(role.getAsMention()).append(", ");
|
||||
}
|
||||
|
||||
String s = rolesAdded.size() > 1 ? "s" : "";
|
||||
logFeature.sendLog(guild, LogType.MEMBER_ROLE_UPDATE, EmbedUtils.successEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Role%s Added".formatted(s))
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Role%s Added: %s".formatted(s, roles.substring(0, roles.length() - 2)), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberRoleRemove(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull List<Role> rolesAdded, @NonNull GuildMemberRoleRemoveEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
|
||||
StringBuilder roles = new StringBuilder();
|
||||
for (Role role : rolesAdded) {
|
||||
roles.append(role.getAsMention()).append(", ");
|
||||
}
|
||||
|
||||
String s = rolesAdded.size() > 1 ? "s" : "";
|
||||
logFeature.sendLog(guild, LogType.MEMBER_ROLE_UPDATE, EmbedUtils.errorEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Role%s Removed".formatted(s))
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Role%s Removed: %s".formatted(s, roles.substring(0, roles.length() - 2)), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberBan(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildBanEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
|
||||
logFeature.sendLog(guild, LogType.MEMBER_BAN, EmbedUtils.errorEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Banned")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberUnban(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildUnbanEvent event) {
|
||||
if (user.getDiscordUser().isBot()) return;
|
||||
|
||||
logFeature.sendLog(guild, LogType.MEMBER_UNBAN, EmbedUtils.successEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Unbanned")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberTimeout(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberUpdateTimeOutEvent event) {
|
||||
OffsetDateTime timeoutEnd = event.getNewTimeOutEnd();
|
||||
if (user.getDiscordUser().isBot() || timeoutEnd == null) return;
|
||||
|
||||
long seconds = timeoutEnd.toInstant().getEpochSecond();
|
||||
logFeature.sendLog(guild, LogType.MEMBER_TIMEOUT, EmbedUtils.errorEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Member Timed Out")
|
||||
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
|
||||
.appendLine("Timeout End: <t:%s>".formatted(seconds), true)
|
||||
.appendLine("Relative End: <t:%s:R>".formatted(seconds), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package cc.fascinated.bat.features.logging.listeners;
|
||||
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.event.EventListener;
|
||||
import cc.fascinated.bat.features.logging.LogFeature;
|
||||
import cc.fascinated.bat.features.logging.LogType;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import cc.fascinated.bat.model.DiscordMessage;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
public class MessageListener implements EventListener {
|
||||
private final LogFeature logFeature;
|
||||
|
||||
@Autowired
|
||||
public MessageListener(@NonNull ApplicationContext context) {
|
||||
this.logFeature = context.getBean(LogFeature.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMessageDelete(@NonNull BatGuild guild, BatUser user, DiscordMessage message, @NonNull MessageDeleteEvent event) {
|
||||
if (user.getDiscordUser().isBot() || message.getAuthor().isBot()) return;
|
||||
|
||||
logFeature.sendLog(guild, LogType.MESSAGE_DELETE, EmbedUtils.errorEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Message Deleted")
|
||||
.appendLine("Author: %s".formatted(message.getAuthor().getAsMention()), true)
|
||||
.appendLine("Channel: %s".formatted(message.getChannel().getAsMention()), true)
|
||||
.appendLine("Content: %s".formatted(logFeature.formatContent(message.getContent())), true)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMessageEdit(@NonNull BatGuild guild, @NonNull BatUser user, DiscordMessage oldMessage,
|
||||
@NonNull DiscordMessage newMessage, @NonNull MessageUpdateEvent event) {
|
||||
if (user.getDiscordUser().isBot() || newMessage.getAuthor().isBot() || oldMessage == null) return;
|
||||
|
||||
logFeature.sendLog(guild, LogType.MESSAGE_EDIT, EmbedUtils.genericEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Message Edited")
|
||||
.appendLine("Author: %s".formatted(newMessage.getAuthor().getAsMention()), true)
|
||||
.appendLine("Channel: %s".formatted(newMessage.getChannel().getAsMention()), true)
|
||||
.appendLine("Old Content: %s".formatted(logFeature.formatContent(oldMessage.getContent())), true)
|
||||
.appendLine("New Content: %s".formatted(logFeature.formatContent(newMessage.getContent())), true)
|
||||
.appendLine("*[Jump to Message](%s)*".formatted(newMessage.getMessageUrl()), false)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package cc.fascinated.bat.features.messagesnipe;
|
||||
|
||||
import cc.fascinated.bat.command.Category;
|
||||
import cc.fascinated.bat.event.EventListener;
|
||||
import cc.fascinated.bat.features.Feature;
|
||||
import cc.fascinated.bat.features.messagesnipe.command.MessageSnipeCommand;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import cc.fascinated.bat.model.DiscordMessage;
|
||||
import cc.fascinated.bat.service.CommandService;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
public class MessageSnipeFeature extends Feature implements EventListener {
|
||||
/**
|
||||
* The sniped messages for each guild
|
||||
*/
|
||||
private static final Map<BatGuild, List<SnipedMessage>> snipedMessages = new HashMap<>();
|
||||
|
||||
@Autowired
|
||||
public MessageSnipeFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
|
||||
super("Message Snipe", false, Category.SNIPE);
|
||||
|
||||
super.registerCommand(commandService, context.getBean(MessageSnipeCommand.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the sniped messages for the given guild
|
||||
*
|
||||
* @param guild the guild
|
||||
* @return if the sniped messages were cleared
|
||||
*/
|
||||
public static boolean clearSnipedMessages(BatGuild guild) {
|
||||
if (snipedMessages.containsKey(guild)) {
|
||||
snipedMessages.remove(guild);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sniped messages for the given guild
|
||||
*
|
||||
* @param guild the guild
|
||||
* @return the sniped messages for the given guild
|
||||
*/
|
||||
public static SnipedMessage getDeletedMessage(BatGuild guild, String channelId) {
|
||||
List<SnipedMessage> messages = snipedMessages.getOrDefault(guild, new ArrayList<>()).stream().filter(message -> message.getDeletedDate() != null)
|
||||
.sorted(Comparator.comparing(SnipedMessage::getDeletedDate).reversed()).toList();
|
||||
for (SnipedMessage message : messages) {
|
||||
if (message.getDeletedDate() != null // Check if the message was deleted
|
||||
&& message.getMessage().getChannel().getId().equals(channelId)) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sniped message with the given id
|
||||
*
|
||||
* @param guild the guild
|
||||
* @param messageId the id of the message
|
||||
* @return the sniped message with the given id
|
||||
*/
|
||||
private SnipedMessage getSnipedMessage(BatGuild guild, String messageId) {
|
||||
List<SnipedMessage> messages = snipedMessages.getOrDefault(guild, new ArrayList<>());
|
||||
for (SnipedMessage message : messages) {
|
||||
if (message.getMessage().getId().equals(messageId)) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
|
||||
if (event.getAuthor().isBot()) return;
|
||||
if (guild.getFeatureProfile().isFeatureDisabled(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SnipedMessage> messages = snipedMessages.getOrDefault(guild, new ArrayList<>());
|
||||
if (messages.size() >= 10) {
|
||||
messages.remove(0);
|
||||
}
|
||||
messages.add(new SnipedMessage(event.getMessage(), null));
|
||||
snipedMessages.put(guild, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMessageDelete(@NonNull BatGuild guild, BatUser user, DiscordMessage message, @NonNull MessageDeleteEvent event) {
|
||||
if (guild.getFeatureProfile().isFeatureDisabled(this)) {
|
||||
return;
|
||||
}
|
||||
List<SnipedMessage> messages = snipedMessages.getOrDefault(guild, new ArrayList<>());
|
||||
if (messages.size() >= 10) {
|
||||
messages.remove(0);
|
||||
}
|
||||
SnipedMessage snipedMessage = getSnipedMessage(guild, event.getMessageId());
|
||||
if (snipedMessage == null) {
|
||||
return;
|
||||
}
|
||||
snipedMessage.setDeletedDate(new Date());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMessageEdit(@NonNull BatGuild guild, @NonNull BatUser user, DiscordMessage oldMessage,
|
||||
@NonNull DiscordMessage newMessage, @NonNull MessageUpdateEvent event) {
|
||||
if (guild.getFeatureProfile().isFeatureDisabled(this)) {
|
||||
return;
|
||||
}
|
||||
List<SnipedMessage> messages = snipedMessages.getOrDefault(guild, new ArrayList<>());
|
||||
if (messages.size() >= 10) {
|
||||
messages.remove(0);
|
||||
}
|
||||
SnipedMessage snipedMessage = getSnipedMessage(guild, event.getMessageId());
|
||||
if (snipedMessage == null) {
|
||||
return;
|
||||
}
|
||||
snipedMessage.setMessage(event.getMessage());
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package cc.fascinated.bat.features.messagesnipe;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class SnipedMessage {
|
||||
/**
|
||||
* The message that was sniped
|
||||
*/
|
||||
private Message message;
|
||||
|
||||
/**
|
||||
* The date when the message was deleted
|
||||
*/
|
||||
private Date deletedDate;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package cc.fascinated.bat.features.messagesnipe.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatSubCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.features.messagesnipe.MessageSnipeFeature;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component("messagesnipe:clear.sub")
|
||||
@CommandInfo(name = "clear", description = "Clears the known sniped messages for this guild", requiredPermissions = Permission.MESSAGE_MANAGE)
|
||||
public class ClearSubCommand extends BatSubCommand {
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
boolean cleared = MessageSnipeFeature.clearSnipedMessages(guild);
|
||||
if (!cleared) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("There are no messages to clear in this guild")
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription("Successfully cleared the sniped messages for this guild")
|
||||
.build()).queue();
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package cc.fascinated.bat.features.messagesnipe.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatSubCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.common.PasteUtils;
|
||||
import cc.fascinated.bat.features.messagesnipe.MessageSnipeFeature;
|
||||
import cc.fascinated.bat.features.messagesnipe.SnipedMessage;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
@CommandInfo(name = "deleted", description = "Snipe the last deleted message in this channel")
|
||||
public class DeletedSubCommand extends BatSubCommand {
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
SnipedMessage message = MessageSnipeFeature.getDeletedMessage(guild, channel.getId());
|
||||
if (message == null) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("There are no deleted messages to snipe in this channel")
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
User author = message.getMessage().getAuthor();
|
||||
String content = message.getMessage().getContentStripped();
|
||||
String formattedContent = content.length() > 512 ? PasteUtils.uploadPaste(content).getUrl() : "```\n%s\n```".formatted(content);
|
||||
event.replyEmbeds(EmbedUtils.genericEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("Deleted Message Snipe")
|
||||
.appendLine("Author: **%s** (%s)".formatted(author.getAsMention(), author.getId()), true)
|
||||
.appendLine("Deleted: <t:%d:R>".formatted(message.getDeletedDate().getTime() / 1000), true)
|
||||
.appendLine("Content: %s".formatted(formattedContent), true)
|
||||
.build()).build()).queue();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cc.fascinated.bat.features.messagesnipe.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
@CommandInfo(name = "snipe", description = "Snipe messages")
|
||||
public class MessageSnipeCommand extends BatCommand {
|
||||
public MessageSnipeCommand(@NonNull ApplicationContext context) {
|
||||
super.addSubCommand(context.getBean(DeletedSubCommand.class));
|
||||
super.addSubCommand(context.getBean(ClearSubCommand.class));
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ public class UserSubCommand extends BatSubCommand {
|
||||
builder.append("%s has no name history".formatted(target.getDiscordUser().getAsMention()));
|
||||
} else {
|
||||
for (TrackedName trackedName : profile.getNameHistorySorted()) {
|
||||
builder.append("`%s` - <t:%s>\n".formatted(trackedName.getName(), trackedName.getChangedDate().toInstant().toEpochMilli()/1000));
|
||||
builder.append("`%s` - <t:%s>\n".formatted(trackedName.getName(), trackedName.getChangedDate().toInstant().getEpochSecond()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ public class ScoreSaberCommand extends BatCommand {
|
||||
.addField("Rank", "#" + NumberFormatter.formatCommas(account.getRank()), true)
|
||||
.addField("Country Rank", "#" + NumberFormatter.formatCommas(account.getCountryRank()), true)
|
||||
.addField("PP", NumberFormatter.formatCommas(account.getPp()), true)
|
||||
.addField("Joined", "<t:%s>".formatted(DateUtils.getDateFromString(account.getFirstSeen()).toInstant().toEpochMilli() / 1000), true)
|
||||
.addField("Joined", "<t:%s>".formatted(DateUtils.getDateFromString(account.getFirstSeen()).toInstant().getEpochSecond()), true)
|
||||
.setTimestamp(LocalDateTime.now())
|
||||
.setFooter(fetchTime > 3 ? "Fetched in %sms".formatted(fetchTime) : "Cached", "https://flagcdn.com/h120/%s.png".formatted(account.getCountry().toLowerCase()))
|
||||
.setColor(Colors.DEFAULT)
|
||||
|
@ -29,7 +29,7 @@ import se.michaelthelin.spotify.model_objects.specification.Track;
|
||||
public class SpotifyFeature extends Feature {
|
||||
@Autowired
|
||||
public SpotifyFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
|
||||
super("Spotify", true,Category.MUSIC);
|
||||
super("Spotify", true, Category.MUSIC);
|
||||
|
||||
super.registerCommand(commandService, context.getBean(SpotifyCommand.class));
|
||||
}
|
||||
|
102
src/main/java/cc/fascinated/bat/features/tmdb/TMDBFeature.java
Normal file
102
src/main/java/cc/fascinated/bat/features/tmdb/TMDBFeature.java
Normal file
@ -0,0 +1,102 @@
|
||||
package cc.fascinated.bat.features.tmdb;
|
||||
|
||||
import cc.fascinated.bat.command.Category;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.common.NumberFormatter;
|
||||
import cc.fascinated.bat.features.Feature;
|
||||
import cc.fascinated.bat.features.tmdb.command.TMDBCommand;
|
||||
import cc.fascinated.bat.service.CommandService;
|
||||
import cc.fascinated.bat.service.TMDBService;
|
||||
import info.movito.themoviedbapi.model.core.Movie;
|
||||
import info.movito.themoviedbapi.model.core.TvSeries;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nick (okNick)
|
||||
*/
|
||||
@Component
|
||||
public class TMDBFeature extends Feature {
|
||||
@Autowired
|
||||
public TMDBFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
|
||||
super("TMDB", true, Category.MOVIES_TV);
|
||||
|
||||
super.registerCommand(commandService, context.getBean(TMDBCommand.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an embed for a movie page.
|
||||
*
|
||||
* @param tmdbService The TMDB service.
|
||||
* @param query The query to search for.
|
||||
* @param language The language to search in.
|
||||
* @param primaryReleaseYear The primary release year to filter by.
|
||||
* @param region The region to search in.
|
||||
* @param year The year to filter by.
|
||||
* @param movie The movie index.
|
||||
* @param adult Whether to include adult content.
|
||||
* @return The movie page embed.
|
||||
*/
|
||||
public static EmbedBuilder pageMovie(@NonNull TMDBService tmdbService, @NonNull String query, String language, String primaryReleaseYear, String region, String year, int movie, boolean adult) {
|
||||
List<Movie> movieList = tmdbService.lookupMovies(query, adult, language, primaryReleaseYear, region, year);
|
||||
|
||||
if (movieList.isEmpty()) {
|
||||
return EmbedUtils.errorEmbed()
|
||||
.setDescription("No movieList found with the provided query + options!");
|
||||
}
|
||||
|
||||
// Adjust movie index to stay within bounds
|
||||
if (movie >= movieList.size()) {
|
||||
movie = movieList.size() - 1; // Set to the last movie if index exceeds list size
|
||||
}
|
||||
|
||||
return EmbedUtils.genericEmbed()
|
||||
.setAuthor(movieList.get(movie).getTitle(), "https://www.themoviedb.org/movie/%s".formatted(movieList.get(movie).getId()))
|
||||
.setThumbnail("https://media.themoviedb.org/t/p/w220_and_h330_face%s".formatted(movieList.get(movie).getPosterPath()))
|
||||
.setDescription(movieList.get(movie).getOverview())
|
||||
.addField("Release Date", movieList.get(movie).getReleaseDate(), true)
|
||||
.addField("Rating", NumberFormatter.format(movieList.get(movie).getVoteAverage()) + "/10", true)
|
||||
.addField("Language", movieList.get(movie).getOriginalLanguage(), true)
|
||||
.setFooter("Page %s of %s".formatted(movie + 1, movieList.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an embed for a series page.
|
||||
*
|
||||
* @param tmdbService The TMDB service.
|
||||
* @param query The query to search for.
|
||||
* @param language The language to search in.
|
||||
* @param firstAirDateYear The first air date year to filter by.
|
||||
* @param year The year to filter by.
|
||||
* @param series The series index.
|
||||
* @param adult Whether to include adult content.
|
||||
* @return The series page embed.
|
||||
*/
|
||||
public static EmbedBuilder pageSeries(@NonNull TMDBService tmdbService, @NonNull String query, String language, int firstAirDateYear, int year, int series, boolean adult) {
|
||||
List<TvSeries> seriesList = tmdbService.lookupSeries(query, adult, language, firstAirDateYear, year);
|
||||
|
||||
if (seriesList.isEmpty()) {
|
||||
return EmbedUtils.errorEmbed()
|
||||
.setDescription("No series found with the provided query + options!");
|
||||
}
|
||||
|
||||
// Adjust series index to stay within bounds
|
||||
if (series >= seriesList.size()) {
|
||||
series = seriesList.size() - 1; // Set to the last series if index exceeds list size
|
||||
}
|
||||
|
||||
return EmbedUtils.genericEmbed()
|
||||
.setAuthor(seriesList.get(series).getName(), "https://www.themoviedb.org/tv/%s".formatted(seriesList.get(series).getId()))
|
||||
.setThumbnail("https://media.themoviedb.org/t/p/w220_and_h330_face%s".formatted(seriesList.get(series).getPosterPath()))
|
||||
.setDescription(seriesList.get(series).getOverview())
|
||||
.addField("First Air Date", seriesList.get(series).getFirstAirDate(), true)
|
||||
.addField("Rating", NumberFormatter.format(seriesList.get(series).getVoteAverage()) + "/10", true)
|
||||
.addField("Language", seriesList.get(series).getOriginalLanguage(), true)
|
||||
.setFooter("Page %s of %s".formatted(series + 1, seriesList.size()));
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package cc.fascinated.bat.features.tmdb.command;
|
||||
|
||||
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.features.tmdb.TMDBFeature;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import cc.fascinated.bat.service.TMDBService;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.entities.emoji.Emoji;
|
||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
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 net.dv8tion.jda.api.interactions.components.buttons.Button;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@CommandInfo(name = "movie", description = "Get information about a movie")
|
||||
public class MovieSubCommand extends BatSubCommand implements EventListener {
|
||||
|
||||
private final TMDBService tmdbService;
|
||||
private final Map<String, Map<String, String>> userCommands; // Map to store user commands and their parameters
|
||||
|
||||
@Autowired
|
||||
public MovieSubCommand(@NonNull TMDBService tmdbService) {
|
||||
this.tmdbService = tmdbService;
|
||||
this.userCommands = new HashMap<>();
|
||||
|
||||
super.addOption(OptionType.STRING, "title", "The title of the movie", true);
|
||||
super.addOption(OptionType.STRING, "language", "A locale code (en-US) to lookup movies in a specific language", false);
|
||||
super.addOption(OptionType.STRING, "primary_release_year", "Filter the results so that only the primary release dates have this value", false);
|
||||
super.addOption(OptionType.STRING, "region", "An ISO 3166-1 code (US) to lookup movies from a specific region", false);
|
||||
super.addOption(OptionType.STRING, "year", "Filter the results release dates to matches that include this value", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
OptionMapping titleOption = event.getOption("title");
|
||||
if (titleOption == null) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("You must provide a title to search for!")
|
||||
.build())
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if the channel is NSFW. If so, allow adult content
|
||||
boolean adult = false;
|
||||
if (event.getChannel() instanceof TextChannel textChannel) {
|
||||
adult = textChannel.isNSFW();
|
||||
}
|
||||
|
||||
OptionMapping languageOption = event.getOption("language");
|
||||
OptionMapping primaryReleaseYearOption = event.getOption("primary_release_year");
|
||||
OptionMapping regionOption = event.getOption("region");
|
||||
OptionMapping yearOption = event.getOption("year");
|
||||
|
||||
// Store user command and parameters for later use
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("title", titleOption.getAsString());
|
||||
if (languageOption != null) params.put("language", languageOption.getAsString());
|
||||
if (primaryReleaseYearOption != null) params.put("primary_release_year", primaryReleaseYearOption.getAsString());
|
||||
if (regionOption != null) params.put("region", regionOption.getAsString());
|
||||
if (yearOption != null) params.put("year", yearOption.getAsString());
|
||||
params.put("adult", String.valueOf(adult));
|
||||
|
||||
userCommands.put(user.getId(), params);
|
||||
|
||||
event.replyEmbeds(TMDBFeature.pageMovie(
|
||||
tmdbService,
|
||||
titleOption.getAsString(),
|
||||
(languageOption != null ? languageOption.getAsString() : null),
|
||||
(primaryReleaseYearOption != null ? primaryReleaseYearOption.getAsString() : null),
|
||||
(regionOption != null ? regionOption.getAsString() : null),
|
||||
(yearOption != null ? yearOption.getAsString() : null),
|
||||
0, // Initial page number
|
||||
adult
|
||||
).build()
|
||||
).addActionRow(
|
||||
Button.primary("backMovie", "Back").withEmoji(Emoji.fromFormatted("⬅️")),
|
||||
Button.primary("nextMovie", "Next").withEmoji(Emoji.fromFormatted("➡️"))
|
||||
).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
|
||||
Map<String, String> params = userCommands.get(user.getId());
|
||||
if (params == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int currentPage = Integer.parseInt(params.getOrDefault("page", "0"));
|
||||
boolean adult = Boolean.parseBoolean(params.get("adult"));
|
||||
|
||||
// Retrieve stored parameters
|
||||
String title = params.get("title");
|
||||
String language = params.get("language");
|
||||
String primaryReleaseYear = params.get("primary_release_year");
|
||||
String region = params.get("region");
|
||||
String year = params.get("year");
|
||||
|
||||
if (event.getComponentId().equals("backMovie")) {
|
||||
currentPage--;
|
||||
if (currentPage < 0) {
|
||||
currentPage = 0; // Ensure currentPage doesn't go negative
|
||||
}
|
||||
} else if (event.getComponentId().equals("nextMovie")) {
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
params.put("page", String.valueOf(currentPage));
|
||||
|
||||
event.editMessageEmbeds(TMDBFeature.pageMovie(
|
||||
tmdbService,
|
||||
title,
|
||||
language,
|
||||
primaryReleaseYear,
|
||||
region,
|
||||
year,
|
||||
currentPage,
|
||||
adult
|
||||
).build()).queue();
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package cc.fascinated.bat.features.tmdb.command;
|
||||
|
||||
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.features.tmdb.TMDBFeature;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import cc.fascinated.bat.service.TMDBService;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.entities.emoji.Emoji;
|
||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
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 net.dv8tion.jda.api.interactions.components.buttons.Button;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@CommandInfo(name = "series", description = "Get information about a series")
|
||||
public class SeriesSubCommand extends BatSubCommand implements EventListener {
|
||||
|
||||
private final TMDBService tmdbService;
|
||||
private final Map<String, Map<String, String>> userCommands; // Map to store user commands and their parameters
|
||||
|
||||
@Autowired
|
||||
public SeriesSubCommand(@NonNull TMDBService tmdbService) {
|
||||
this.tmdbService = tmdbService;
|
||||
this.userCommands = new HashMap<>();
|
||||
|
||||
super.addOption(OptionType.STRING, "title", "The title of the series", true);
|
||||
super.addOption(OptionType.STRING, "language", "A locale code (en-US) to lookup movies in a specific language", false);
|
||||
super.addOption(OptionType.INTEGER, "first_air_year", "Filter the results so that only the first air year has this value", false);
|
||||
super.addOption(OptionType.INTEGER, "year", "Filter the results release dates to matches that include this value", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
OptionMapping titleOption = event.getOption("title");
|
||||
if (titleOption == null) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("You must provide a title to search for!")
|
||||
.build())
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if the channel is NSFW. If so, allow adult content
|
||||
boolean adult = false;
|
||||
if (event.getChannel() instanceof TextChannel textChannel) {
|
||||
adult = textChannel.isNSFW();
|
||||
}
|
||||
|
||||
OptionMapping languageOption = event.getOption("language");
|
||||
OptionMapping firstAirYearOption = event.getOption("first_air_year");
|
||||
OptionMapping yearOption = event.getOption("year");
|
||||
|
||||
// Store user command and parameters for later use
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("title", titleOption.getAsString());
|
||||
if (languageOption != null) params.put("language", languageOption.getAsString());
|
||||
if (firstAirYearOption != null) params.put("first_air_year", String.valueOf(firstAirYearOption.getAsInt()));
|
||||
if (yearOption != null) params.put("year", String.valueOf(yearOption.getAsInt()));
|
||||
params.put("adult", String.valueOf(adult));
|
||||
|
||||
userCommands.put(user.getId(), params);
|
||||
|
||||
event.replyEmbeds(TMDBFeature.pageSeries(
|
||||
tmdbService,
|
||||
titleOption.getAsString(),
|
||||
(languageOption != null ? languageOption.getAsString() : null),
|
||||
(firstAirYearOption != null ? firstAirYearOption.getAsInt() : -1),
|
||||
(yearOption != null ? yearOption.getAsInt() : -1),
|
||||
0, // Initial page number
|
||||
adult
|
||||
).build()
|
||||
).addActionRow(
|
||||
Button.primary("backSeries", "Back").withEmoji(Emoji.fromFormatted("⬅️")),
|
||||
Button.primary("nextSeries", "Next").withEmoji(Emoji.fromFormatted("➡️"))
|
||||
).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
|
||||
Map<String, String> params = userCommands.get(user.getId());
|
||||
if (params == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int currentPage = Integer.parseInt(params.getOrDefault("page", "0"));
|
||||
boolean adult = Boolean.parseBoolean(params.get("adult"));
|
||||
|
||||
// Retrieve stored parameters
|
||||
String title = params.get("title");
|
||||
String language = params.get("language");
|
||||
int firstAirYear = (params.get("first_air_year") != null ? Integer.parseInt(params.get("first_air_year")) : -1);
|
||||
int year = (params.get("year") != null ? Integer.parseInt(params.get("year")) : -1);
|
||||
|
||||
if (event.getComponentId().equals("backSeries")) {
|
||||
currentPage--;
|
||||
if (currentPage < 0) {
|
||||
currentPage = 0; // Ensure currentPage doesn't go negative
|
||||
}
|
||||
} else if (event.getComponentId().equals("nextSeries")) {
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
params.put("page", String.valueOf(currentPage));
|
||||
|
||||
event.editMessageEmbeds(TMDBFeature.pageSeries(
|
||||
tmdbService,
|
||||
title,
|
||||
language,
|
||||
firstAirYear,
|
||||
year,
|
||||
currentPage,
|
||||
adult
|
||||
).build()).queue();
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cc.fascinated.bat.features.tmdb.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Nick (okNick)
|
||||
*/
|
||||
@Component
|
||||
@CommandInfo(name = "tmdb", description = "Get information about movies and TV shows", guildOnly = false)
|
||||
public class TMDBCommand extends BatCommand {
|
||||
@Autowired
|
||||
public TMDBCommand(@NonNull ApplicationContext context) {
|
||||
super.addSubCommand(context.getBean(MovieSubCommand.class));
|
||||
super.addSubCommand(context.getBean(SeriesSubCommand.class));
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import cc.fascinated.bat.common.ProfileHolder;
|
||||
import cc.fascinated.bat.common.Serializable;
|
||||
import cc.fascinated.bat.features.base.profile.FeatureProfile;
|
||||
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
|
||||
import cc.fascinated.bat.features.logging.LogProfile;
|
||||
import cc.fascinated.bat.features.namehistory.profile.guild.NameHistoryProfile;
|
||||
import cc.fascinated.bat.premium.PremiumProfile;
|
||||
import cc.fascinated.bat.service.DiscordService;
|
||||
@ -109,10 +110,20 @@ public class BatGuild extends ProfileHolder {
|
||||
return getProfile(BirthdayProfile.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the log profile
|
||||
*
|
||||
* @return the log profile
|
||||
*/
|
||||
public LogProfile getLogProfile() {
|
||||
return getProfile(LogProfile.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the user
|
||||
*/
|
||||
public void save() {
|
||||
org.bson.Document document = new org.bson.Document();
|
||||
document.put("_id", id);
|
||||
document.put("createdAt", createdAt);
|
||||
|
||||
@ -124,7 +135,7 @@ public class BatGuild extends ProfileHolder {
|
||||
|
||||
MongoService.INSTANCE.getGuildsCollection().replaceOne(
|
||||
new org.bson.Document("_id", id),
|
||||
this.getDocument(),
|
||||
document,
|
||||
new ReplaceOptions().upsert(true)
|
||||
);
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ public class BatUser extends ProfileHolder {
|
||||
* Saves the user
|
||||
*/
|
||||
public void save() {
|
||||
org.bson.Document document = new org.bson.Document();
|
||||
document.put("_id", id);
|
||||
document.put("createdAt", createdAt);
|
||||
|
||||
@ -114,7 +115,7 @@ public class BatUser extends ProfileHolder {
|
||||
|
||||
MongoService.INSTANCE.getUsersCollection().replaceOne(
|
||||
new org.bson.Document("_id", id),
|
||||
this.getDocument(),
|
||||
document,
|
||||
new ReplaceOptions().upsert(true)
|
||||
);
|
||||
}
|
||||
|
79
src/main/java/cc/fascinated/bat/model/DiscordMessage.java
Normal file
79
src/main/java/cc/fascinated/bat/model/DiscordMessage.java
Normal file
@ -0,0 +1,79 @@
|
||||
package cc.fascinated.bat.model;
|
||||
|
||||
import cc.fascinated.bat.service.DiscordService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter @Setter
|
||||
@RedisHash(value = "DiscordMessage", timeToLive = 86400) // 24 hours
|
||||
public class DiscordMessage {
|
||||
/**
|
||||
* The snowflake ID of the message
|
||||
*/
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* The timestamp when the message was sent
|
||||
*/
|
||||
private final long timestamp;
|
||||
|
||||
/**
|
||||
* The snowflake ID of the channel the message was sent in
|
||||
*/
|
||||
private final String channelId;
|
||||
|
||||
/**
|
||||
* The snowflake ID of the guild the message was sent in
|
||||
*/
|
||||
private final String guildId;
|
||||
|
||||
/**
|
||||
* The snowflake ID of the author of the message
|
||||
*/
|
||||
private final String authorId;
|
||||
|
||||
/**
|
||||
* The content of the message
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* Whether the message was deleted
|
||||
*/
|
||||
private boolean deleted;
|
||||
|
||||
/**
|
||||
* Gets the author of the message
|
||||
*
|
||||
* @return the author
|
||||
*/
|
||||
public User getAuthor() {
|
||||
return DiscordService.JDA.getUserById(this.authorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the channel the message was sent in
|
||||
*
|
||||
* @return the channel
|
||||
*/
|
||||
public TextChannel getChannel() {
|
||||
return DiscordService.JDA.getTextChannelById(this.channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of the message
|
||||
*
|
||||
* @return the URL
|
||||
*/
|
||||
public String getMessageUrl() {
|
||||
return "https://discord.com/channels/%s/%s/%s".formatted(this.guildId, this.channelId, this.id);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cc.fascinated.bat.model.token.paste;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter @Setter
|
||||
public class PasteUploadToken {
|
||||
/**
|
||||
* The key of the paste
|
||||
*/
|
||||
private final String key;
|
||||
|
||||
/**
|
||||
* The url of the paste
|
||||
*/
|
||||
private String url;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package cc.fascinated.bat.repository;
|
||||
|
||||
import cc.fascinated.bat.model.DiscordMessage;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
public interface DiscordMessageRepository extends CrudRepository<DiscordMessage, String> {}
|
@ -75,7 +75,7 @@ public class CommandService extends ListenerAdapter {
|
||||
JDA jda = DiscordService.JDA;
|
||||
long before = System.currentTimeMillis();
|
||||
|
||||
Guild adminGuild = jda.getGuildById(Consts.ADMIN_GUILD);
|
||||
Guild adminGuild = jda.getGuildById(Config.INSTANCE.getAdminGuild());
|
||||
if (!Config.isProduction()) {
|
||||
if (adminGuild == null) {
|
||||
log.error("Unable to find the admin guild to register commands");
|
||||
|
@ -0,0 +1,104 @@
|
||||
package cc.fascinated.bat.service;
|
||||
|
||||
import cc.fascinated.bat.event.EventListener;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import cc.fascinated.bat.model.DiscordMessage;
|
||||
import cc.fascinated.bat.repository.DiscordMessageRepository;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Service
|
||||
@Log4j2
|
||||
@DependsOn("discordService")
|
||||
public class DiscordMessageService extends ListenerAdapter {
|
||||
private final DiscordMessageRepository discordMessageRepository;
|
||||
private final GuildService guildService;
|
||||
private final UserService userService;
|
||||
|
||||
@Autowired
|
||||
public DiscordMessageService(DiscordMessageRepository discordMessageRepository, @NonNull GuildService guildService,
|
||||
@NonNull UserService userService) {
|
||||
this.discordMessageRepository = discordMessageRepository;
|
||||
this.guildService = guildService;
|
||||
this.userService = userService;
|
||||
|
||||
DiscordService.JDA.addEventListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message with the given id
|
||||
*
|
||||
* @param messageId the id of the message
|
||||
* @return the message with the given id
|
||||
*/
|
||||
public DiscordMessage getMessage(String messageId) {
|
||||
Optional<DiscordMessage> optionalMessage = discordMessageRepository.findById(messageId);
|
||||
return optionalMessage.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
|
||||
discordMessageRepository.save(new DiscordMessage(
|
||||
event.getMessageId(),
|
||||
event.getMessage().getTimeCreated().toInstant().toEpochMilli(),
|
||||
event.getChannel().getId(),
|
||||
event.getGuild().getId(),
|
||||
event.getAuthor().getId(),
|
||||
event.getMessage().getContentStripped(),
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageUpdate(@NotNull MessageUpdateEvent event) {
|
||||
Optional<DiscordMessage> message = discordMessageRepository.findById(event.getMessageId());
|
||||
DiscordMessage oldMessage = message.orElse(null);
|
||||
if (oldMessage != null) {
|
||||
if (oldMessage.getContent().equals(event.getMessage().getContentStripped())) {
|
||||
return;
|
||||
}
|
||||
discordMessageRepository.delete(oldMessage);
|
||||
}
|
||||
DiscordMessage newMessage = new DiscordMessage(
|
||||
event.getMessageId(),
|
||||
event.getMessage().getTimeCreated().toInstant().toEpochMilli(),
|
||||
event.getChannel().getId(),
|
||||
event.getGuild().getId(),
|
||||
event.getAuthor().getId(),
|
||||
event.getMessage().getContentStripped(),
|
||||
false
|
||||
);
|
||||
discordMessageRepository.save(newMessage);
|
||||
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
BatUser user = userService.getUser(event.getAuthor().getId());
|
||||
for (EventListener listener : EventService.LISTENERS) {
|
||||
listener.onGuildMessageEdit(guild, user, oldMessage, newMessage, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageDelete(@NotNull MessageDeleteEvent event) {
|
||||
Optional<DiscordMessage> optionalMessage = discordMessageRepository.findById(event.getMessageId());
|
||||
if (optionalMessage.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
DiscordMessage message = optionalMessage.get();
|
||||
message.setDeleted(true);
|
||||
discordMessageRepository.save(message);
|
||||
}
|
||||
}
|
@ -2,7 +2,9 @@ package cc.fascinated.bat.service;
|
||||
|
||||
import cc.fascinated.bat.common.NumberFormatter;
|
||||
import cc.fascinated.bat.common.TimerUtils;
|
||||
import cc.fascinated.bat.config.Config;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
@ -11,6 +13,7 @@ import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.EnumSet;
|
||||
@ -32,24 +35,27 @@ public class DiscordService {
|
||||
"{guilds} guilds",
|
||||
"{users} users",
|
||||
"your ScoreSaber scores",
|
||||
"/help for help"
|
||||
"/help for help",
|
||||
"/features to toggle features"
|
||||
);
|
||||
|
||||
@Autowired
|
||||
public DiscordService(
|
||||
@NonNull ApplicationContext context,
|
||||
@Value("${discord.token}") String token
|
||||
) throws Exception {
|
||||
context.getBean(Config.class); // Ensure the config is loaded
|
||||
log.info("Starting Discord bot...");
|
||||
JDA = JDABuilder.create(token, EnumSet.of(
|
||||
GatewayIntent.GUILD_MESSAGES,
|
||||
GatewayIntent.MESSAGE_CONTENT,
|
||||
GatewayIntent.GUILD_MEMBERS,
|
||||
GatewayIntent.GUILD_EMOJIS_AND_STICKERS,
|
||||
GatewayIntent.GUILD_PRESENCES
|
||||
GatewayIntent.GUILD_PRESENCES,
|
||||
GatewayIntent.GUILD_VOICE_STATES
|
||||
))
|
||||
.disableCache(
|
||||
CacheFlag.ACTIVITY,
|
||||
CacheFlag.VOICE_STATE,
|
||||
CacheFlag.CLIENT_STATUS,
|
||||
CacheFlag.SCHEDULED_EVENTS
|
||||
).build()
|
||||
|
@ -3,16 +3,27 @@ package cc.fascinated.bat.service;
|
||||
import cc.fascinated.bat.event.EventListener;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import cc.fascinated.bat.model.DiscordMessage;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
|
||||
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.guild.GuildBanEvent;
|
||||
import net.dv8tion.jda.api.events.guild.GuildUnbanEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleAddEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleRemoveEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateAvatarEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateNameEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -36,15 +47,18 @@ public class EventService extends ListenerAdapter {
|
||||
public static final Set<EventListener> LISTENERS = new HashSet<>();
|
||||
private final GuildService guildService;
|
||||
private final UserService userService;
|
||||
private final DiscordMessageService discordMessageService;
|
||||
|
||||
@Autowired
|
||||
public EventService(@NonNull GuildService guildService, @NonNull UserService userService, @NonNull ApplicationContext context) {
|
||||
public EventService(@NonNull ApplicationContext context, @NonNull GuildService guildService, @NonNull UserService userService,
|
||||
@NonNull DiscordMessageService discordMessageService) {
|
||||
this.guildService = guildService;
|
||||
this.userService = userService;
|
||||
this.discordMessageService = discordMessageService;
|
||||
DiscordService.JDA.addEventListener(this);
|
||||
|
||||
context.getBeansOfType(EventListener.class).values().forEach(this::registerListeners);
|
||||
log.info("Registered {} listeners.", LISTENERS.size());
|
||||
log.info("Registered {} event listeners.", LISTENERS.size());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,6 +109,17 @@ public class EventService extends ListenerAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageDelete(@NotNull MessageDeleteEvent event) {
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
DiscordMessage message = discordMessageService.getMessage(event.getMessageId());
|
||||
BatUser user = message == null ? null : userService.getUser(message.getAuthorId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onGuildMessageDelete(guild, user, message, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStringSelectInteraction(StringSelectInteractionEvent event) {
|
||||
if (event.getUser().isBot()) {
|
||||
@ -158,4 +183,111 @@ public class EventService extends ListenerAdapter {
|
||||
listener.onGuildMemberUpdateNickname(guild, user, event.getOldNickname(), event.getNewNickname(), event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberRoleAdd(@NotNull GuildMemberRoleAddEvent event) {
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
BatUser user = userService.getUser(event.getUser().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onGuildMemberRoleAdd(guild, user, event.getRoles(), event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberRoleRemove(@NotNull GuildMemberRoleRemoveEvent event) {
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
BatUser user = userService.getUser(event.getUser().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onGuildMemberRoleRemove(guild, user, event.getRoles(), event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelCreate(@NotNull ChannelCreateEvent event) {
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onChannelCreate(guild, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelDelete(@NotNull ChannelDeleteEvent event) {
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onChannelDelete(guild, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildBan(@NotNull GuildBanEvent event) {
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
BatUser user = userService.getUser(event.getUser().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onGuildMemberBan(guild, user, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildUnban(@NotNull GuildUnbanEvent event) {
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
BatUser user = userService.getUser(event.getUser().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onGuildMemberUnban(guild, user, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberUpdateTimeOut(@NotNull GuildMemberUpdateTimeOutEvent event) {
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
BatGuild guild = guildService.getGuild(event.getGuild().getId());
|
||||
BatUser user = userService.getUser(event.getUser().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onGuildMemberTimeout(guild, user, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserUpdateName(@NotNull UserUpdateNameEvent event) {
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
BatUser user = userService.getUser(event.getUser().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onUserUpdateName(user, event.getOldName(), event.getNewName(), event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserUpdateAvatar(@NotNull UserUpdateAvatarEvent event) {
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
BatUser user = userService.getUser(event.getUser().getId());
|
||||
|
||||
for (EventListener listener : LISTENERS) {
|
||||
listener.onUserUpdateAvatar(user, event.getOldAvatarUrl(), event.getNewAvatarUrl(), event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
77
src/main/java/cc/fascinated/bat/service/TMDBService.java
Normal file
77
src/main/java/cc/fascinated/bat/service/TMDBService.java
Normal file
@ -0,0 +1,77 @@
|
||||
package cc.fascinated.bat.service;
|
||||
|
||||
import info.movito.themoviedbapi.TmdbApi;
|
||||
import info.movito.themoviedbapi.model.core.Movie;
|
||||
import info.movito.themoviedbapi.model.core.MovieResultsPage;
|
||||
import info.movito.themoviedbapi.model.core.TvSeries;
|
||||
import info.movito.themoviedbapi.model.core.TvSeriesResultsPage;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nick (okNick)
|
||||
*/
|
||||
@Service
|
||||
@Getter
|
||||
@Log4j2(topic = "TMDB Service")
|
||||
public class TMDBService {
|
||||
/**
|
||||
* The API key.
|
||||
*/
|
||||
private final String apiKey;
|
||||
|
||||
/**
|
||||
* The TMDB API instance.
|
||||
*/
|
||||
private final TmdbApi tmdbApi;
|
||||
|
||||
public TMDBService(@Value("${tmdb.api-key}") String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
|
||||
this.tmdbApi = new TmdbApi(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup movies based on the provided query and options.
|
||||
*
|
||||
* @param query The query to search for
|
||||
* @param includeAdult Whether to include adult content
|
||||
* @param language The language to search in
|
||||
* @param primaryReleaseYear The primary release year to filter by
|
||||
* @param region The region to search in
|
||||
* @param year The year to filter by
|
||||
* @return The list of movies found with the provided query and options
|
||||
*/
|
||||
@SneakyThrows
|
||||
public List<Movie> lookupMovies(String query, boolean includeAdult, String language, String primaryReleaseYear, String region, String year) {
|
||||
MovieResultsPage movies = tmdbApi.getSearch().searchMovie(query, includeAdult, language, primaryReleaseYear, 1, region, year);
|
||||
if (movies.getTotalResults() == 0) {
|
||||
return null;
|
||||
}
|
||||
return movies.getResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup series based on the provided query and options.
|
||||
*
|
||||
* @param query The query to search for
|
||||
* @param includeAdult Whether to include adult content
|
||||
* @param language The language to search in
|
||||
* @param firstAirDateYear The first air date year to filter by
|
||||
* @param year The year to filter by
|
||||
* @return The list of series found with the provided query and options
|
||||
*/
|
||||
@SneakyThrows
|
||||
public List<TvSeries> lookupSeries(String query, boolean includeAdult, String language, int firstAirDateYear, int year) {
|
||||
TvSeriesResultsPage series = tmdbApi.getSearch().searchTv(query, firstAirDateYear, includeAdult, language, 1, year);
|
||||
if (series.getTotalResults() == 0) {
|
||||
return null;
|
||||
}
|
||||
return series.getResults();
|
||||
}
|
||||
}
|
@ -2,11 +2,13 @@ package cc.fascinated.bat.service;
|
||||
|
||||
import cc.fascinated.bat.common.TimerUtils;
|
||||
import cc.fascinated.bat.event.EventListener;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
|
||||
import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent;
|
||||
import org.bson.Document;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -83,6 +85,11 @@ public class UserService implements EventListener {
|
||||
log.info("Saved all users.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberJoin(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberJoinEvent event) {
|
||||
user.setGlobalName(event.getUser().getName()); // Ensure the user's name is up-to-date
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserUpdateGlobalName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateGlobalNameEvent event) {
|
||||
log.info("User \"{}\" changed their name from \"{}\" to \"{}\"", user.getName(), oldName, newName);
|
||||
|
@ -2,6 +2,12 @@
|
||||
discord:
|
||||
token: "oh my goodnesssssssssss"
|
||||
|
||||
# Bat Configuration
|
||||
bat:
|
||||
# This is where commands will be registered (whilst in development mode)
|
||||
# also where bot owner only commands will be registered
|
||||
admin-guild: 1203163422498361404
|
||||
|
||||
# Sentry Configuration
|
||||
sentry:
|
||||
dsn: "CHANGE_ME"
|
||||
@ -20,6 +26,12 @@ spotify:
|
||||
client-id: "spotify-client-id"
|
||||
client-secret: "spotify-client-secret"
|
||||
|
||||
# TMDB Configuration
|
||||
tmdb:
|
||||
# API Read Access Token
|
||||
api-key: "api-read-access-token"
|
||||
|
||||
|
||||
# Spring Configuration
|
||||
spring:
|
||||
data:
|
||||
@ -27,4 +39,11 @@ spring:
|
||||
mongodb:
|
||||
uri: "mongodb://bat:p4$$w0rd@localhost:27017"
|
||||
database: "bat"
|
||||
auto-index-creation: true # Automatically create collection indexes
|
||||
auto-index-creation: true # Automatically create collection indexes
|
||||
|
||||
# Redis - This is used for caching
|
||||
redis:
|
||||
host: "localhost"
|
||||
port: 6379
|
||||
database: 0
|
||||
auth: "" # Leave blank for no auth
|
30
terms-of-service.txt
Normal file
30
terms-of-service.txt
Normal file
@ -0,0 +1,30 @@
|
||||
Terms of Service (ToS)
|
||||
1. Introduction
|
||||
|
||||
Welcome to Bat! These Terms of Service ("ToS") govern your use of our Discord bot (the "Service"). By using the Service, you agree to be bound by these terms. If you do not agree, do not use the Service.
|
||||
2. Use of Service
|
||||
|
||||
Eligibility: You must comply with Discord’s Terms of Service.
|
||||
License: We grant you a limited, non-exclusive, non-transferable, and revocable license to use the Service.
|
||||
Prohibited Conduct: You agree not to misuse the Service. Prohibited actions include, but are not limited to, spamming, harassment, or engaging in any illegal activities.
|
||||
|
||||
3. Content
|
||||
|
||||
User Content: You are responsible for any content you post or share using the Service. We do not claim ownership of your content, but you grant us a license to use it for the purpose of operating the Service.
|
||||
Bot Content: We strive to ensure our bot provides accurate and helpful content, but we do not guarantee its accuracy.
|
||||
|
||||
4. Privacy Policy
|
||||
|
||||
Your privacy is important to us. Please refer to our Privacy Policy to understand how we collect, use, and protect your information.
|
||||
5. Limitation of Liability
|
||||
|
||||
To the fullest extent permitted by law, we shall not be liable for any indirect, incidental, special, or consequential damages arising out of or in connection with your use of the Service.
|
||||
6. Termination
|
||||
|
||||
We reserve the right to suspend or terminate your access to the Service at any time, with or without cause or notice.
|
||||
7. Changes to the ToS
|
||||
|
||||
We may modify these ToS at any time. We will notify you of any changes by posting the new ToS on our https://discord.gg/yjj2U3ctEG.
|
||||
8. Contact Us
|
||||
|
||||
If you have any questions about these ToS, please contact us at bat@fascinated.cc.
|
Loading…
x
Reference in New Issue
Block a user