├── checkstyle └── suppressions.xml ├── settings.gradle.kts ├── src ├── main │ ├── resources │ │ ├── database │ │ │ └── migrations │ │ │ │ ├── 12-17-2022_remove_channel_reservations.sql │ │ │ │ ├── 12-18-2022_qotw_clear_points.sql │ │ │ │ ├── 12-17-2022_remove_help_transaction_message.sql │ │ │ │ ├── 07-08-2023_channel_transactions.sql │ │ │ │ ├── 09-15-2024_custom_vcs.sql │ │ │ │ ├── 11-11-2024_store_qotw_champion.sql │ │ │ │ ├── 08-15-2023_staff_activity.sql │ │ │ │ ├── 02-13-2023_message_cache_links.sql │ │ │ │ └── 12-17-2022_qotw_point_timestamps.sql │ │ ├── quartz.properties │ │ ├── help_overview │ │ │ └── overview_image_url.txt │ │ ├── assets │ │ │ ├── fonts │ │ │ │ └── Uni-Sans-Heavy.ttf │ │ │ └── images │ │ │ │ ├── LeaderboardUserCard.png │ │ │ │ └── QuestionOfTheWeekHeader.png │ │ ├── help_guidelines │ │ │ ├── guidelines_image_url.txt │ │ │ └── guidelines_text.txt │ │ ├── application.properties │ │ └── logback.xml │ └── java │ │ └── net │ │ └── discordjug │ │ └── javabot │ │ ├── systems │ │ ├── package-info.java │ │ ├── user_preferences │ │ │ ├── model │ │ │ │ ├── StringPreference.java │ │ │ │ ├── UserPreference.java │ │ │ │ ├── PreferenceType.java │ │ │ │ ├── BooleanPreference.java │ │ │ │ └── Preference.java │ │ │ ├── commands │ │ │ │ ├── PreferencesCommand.java │ │ │ │ └── PreferencesListSubcommand.java │ │ │ └── dao │ │ │ │ └── UserPreferenceRepository.java │ │ ├── qotw │ │ │ ├── model │ │ │ │ ├── QOTWAccount.java │ │ │ │ ├── QOTWSubmission.java │ │ │ │ └── QOTWQuestion.java │ │ │ ├── package-info.java │ │ │ ├── submissions │ │ │ │ ├── SubmissionStatus.java │ │ │ │ └── SubmissionInteractionManager.java │ │ │ ├── commands │ │ │ │ ├── QOTWSubcommand.java │ │ │ │ └── qotw_points │ │ │ │ │ ├── DecrementPointsSubcommand.java │ │ │ │ │ └── IncrementPointsSubcommand.java │ │ │ ├── dao │ │ │ │ └── QOTWChampionRepository.java │ │ │ └── jobs │ │ │ │ ├── QOTWReviewReminderJob.java │ │ │ │ ├── QOTWReminderJob.java │ │ │ │ └── QOTWChampionJob.java │ │ ├── starboard │ │ │ └── model │ │ │ │ └── StarboardEntry.java │ │ ├── staff_activity │ │ │ ├── StaffActivityType.java │ │ │ ├── model │ │ │ │ └── StaffActivityMessage.java │ │ │ ├── StaffActivityListener.java │ │ │ └── dao │ │ │ │ └── StaffActivityMessageRepository.java │ │ ├── help │ │ │ ├── model │ │ │ │ └── HelpTransaction.java │ │ │ ├── checks │ │ │ │ ├── ChannelSemanticData.java │ │ │ │ ├── ChannelSemanticCheck.java │ │ │ │ └── SimpleGreetingCheck.java │ │ │ ├── commands │ │ │ │ ├── HelpCommand.java │ │ │ │ ├── HelpGuidelinesSubcommand.java │ │ │ │ └── CloseCommand.java │ │ │ └── HelpExperienceJob.java │ │ ├── custom_vc │ │ │ ├── commands │ │ │ │ ├── CustomVCControlCommand.java │ │ │ │ ├── CustomVCAddMemberSubcommand.java │ │ │ │ └── CustomVCRemoveMemberSubcommand.java │ │ │ └── CustomVCRepository.java │ │ ├── moderation │ │ │ ├── timeout │ │ │ │ ├── TimeoutCommand.java │ │ │ │ └── TimeoutSubcommand.java │ │ │ ├── warn │ │ │ │ ├── model │ │ │ │ │ ├── Warn.java │ │ │ │ │ └── WarnSeverity.java │ │ │ │ ├── WarnCommand.java │ │ │ │ ├── WarnsListContext.java │ │ │ │ ├── DiscardWarnByIdSubCommand.java │ │ │ │ └── DiscardAllWarnsSubcommand.java │ │ │ ├── CommandModerationPermissions.java │ │ │ ├── server_lock │ │ │ │ ├── ServerLockCommand.java │ │ │ │ └── CheckLockStatusSubcommand.java │ │ │ ├── report │ │ │ │ ├── ReportUserContext.java │ │ │ │ ├── ReportMessageContext.java │ │ │ │ └── ReportCommand.java │ │ │ ├── ModerateCommand.java │ │ │ ├── BanCommand.java │ │ │ ├── KickCommand.java │ │ │ └── UnbanCommand.java │ │ ├── staff_commands │ │ │ ├── tags │ │ │ │ ├── model │ │ │ │ │ └── CustomTag.java │ │ │ │ └── commands │ │ │ │ │ ├── TagsCommand.java │ │ │ │ │ ├── TagsAdminCommand.java │ │ │ │ │ └── TagsSubcommand.java │ │ │ ├── self_roles │ │ │ │ └── SelfRoleCommand.java │ │ │ ├── embeds │ │ │ │ ├── EmbedCommand.java │ │ │ │ └── EmbedSubcommand.java │ │ │ ├── role_emoji │ │ │ │ └── RoleEmojiCommand.java │ │ │ └── suggestions │ │ │ │ └── SuggestionCommand.java │ │ ├── user_commands │ │ │ ├── PingCommand.java │ │ │ ├── leaderboard │ │ │ │ └── LeaderboardCommand.java │ │ │ ├── UptimeCommand.java │ │ │ ├── format_code │ │ │ │ ├── FormatCodeMessageContext.java │ │ │ │ └── FormatAndIndentCodeMessageContext.java │ │ │ ├── AvatarCommand.java │ │ │ ├── search │ │ │ │ ├── SearchWebMessageContext.java │ │ │ │ └── SearchWebCommand.java │ │ │ └── BotInfoCommand.java │ │ ├── configuration │ │ │ ├── ConfigCommand.java │ │ │ ├── ExportConfigSubcommand.java │ │ │ └── GetConfigSubcommand.java │ │ └── notification │ │ │ ├── GuildNotificationService.java │ │ │ ├── UserNotificationService.java │ │ │ ├── QOTWGuildNotificationService.java │ │ │ └── NotificationService.java │ │ ├── data │ │ ├── config │ │ │ ├── InvalidPropertyValueException.java │ │ │ ├── guild │ │ │ │ ├── ServerLockConfig.java │ │ │ │ ├── MetricsConfig.java │ │ │ │ ├── StarboardConfig.java │ │ │ │ ├── MessageCacheConfig.java │ │ │ │ └── QOTWConfig.java │ │ │ ├── GuildConfigItem.java │ │ │ └── UnknownPropertyException.java │ │ └── h2db │ │ │ ├── StatementModifier.java │ │ │ ├── ConnectionConsumer.java │ │ │ ├── ResultSetMapper.java │ │ │ ├── DaoConsumer.java │ │ │ ├── TransactionFunction.java │ │ │ ├── ConnectionFunction.java │ │ │ ├── message_cache │ │ │ └── model │ │ │ │ └── CachedMessage.java │ │ │ ├── MigrationUtils.java │ │ │ ├── TableProperty.java │ │ │ └── commands │ │ │ └── DbAdminCommand.java │ │ ├── util │ │ ├── Pair.java │ │ ├── Constants.java │ │ ├── UserUtils.java │ │ ├── ColorUtils.java │ │ ├── ExceptionLogger.java │ │ ├── MessageUtils.java │ │ ├── QueryBuilder.java │ │ ├── StringResourceCache.java │ │ ├── ImageCache.java │ │ └── ImageGenerationUtils.java │ │ ├── api │ │ ├── routes │ │ │ ├── data │ │ │ │ └── UserData.java │ │ │ ├── metrics │ │ │ │ ├── model │ │ │ │ │ └── MetricsData.java │ │ │ │ └── MetricsController.java │ │ │ ├── CaffeineCache.java │ │ │ ├── user_profile │ │ │ │ └── model │ │ │ │ │ ├── UserProfileData.java │ │ │ │ │ └── HelpAccountData.java │ │ │ ├── CorsFilter.java │ │ │ └── leaderboard │ │ │ │ ├── qotw │ │ │ │ └── model │ │ │ │ │ └── QOTWUserData.java │ │ │ │ └── help_experience │ │ │ │ └── model │ │ │ │ └── ExperienceUserData.java │ │ ├── exception │ │ │ ├── InternalServerException.java │ │ │ ├── ErrorResponse.java │ │ │ └── InvalidEntityIdException.java │ │ └── TomcatConfig.java │ │ ├── annotations │ │ ├── AutoDetectableComponentHandler.java │ │ └── PreRegisteredListener.java │ │ ├── listener │ │ ├── filter │ │ │ ├── MessageContent.java │ │ │ ├── MessageModificationStatus.java │ │ │ ├── MessageFilter.java │ │ │ └── BlacklistedMessageAttachmentFilter.java │ │ ├── ShareKnowledgeVoteListener.java │ │ ├── JobChannelVoteListener.java │ │ ├── QOTWSubmissionListener.java │ │ └── VotingRegulationListener.java │ │ └── tasks │ │ └── MetricsUpdater.java └── test │ └── java │ └── net │ └── discordjug │ └── javabot │ ├── package-info.java │ ├── util │ ├── StringUtilsTest.java │ ├── IndentationHelperTest.java │ └── TimeUtilsTest.java │ └── systems │ └── qotw │ └── QOTWPointsServiceTest.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature_request.md └── workflows │ └── build.yml ├── .gitignore └── Dockerfile /checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "javabot" 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/database/migrations/12-17-2022_remove_channel_reservations.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE reserved_help_channels; -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Java-Discord/JavaBot/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/database/migrations/12-18-2022_qotw_clear_points.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM qotw_points WHERE user_id=374328434677121036; 2 | -------------------------------------------------------------------------------- /src/main/resources/quartz.properties: -------------------------------------------------------------------------------- 1 | # Configuration properties for the quartz scheduler. 2 | org.quartz.threadPool.threadCount=1 3 | -------------------------------------------------------------------------------- /src/main/resources/database/migrations/12-17-2022_remove_help_transaction_message.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE help_transaction DROP COLUMN messagetype; -------------------------------------------------------------------------------- /src/main/resources/database/migrations/07-08-2023_channel_transactions.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE help_transaction ADD COLUMN channel BIGINT DEFAULT -1; -------------------------------------------------------------------------------- /src/main/resources/help_overview/overview_image_url.txt: -------------------------------------------------------------------------------- 1 | https://cdn.discordapp.com/attachments/674585018697515048/1004325439210266624/HelpNew.png -------------------------------------------------------------------------------- /src/main/resources/assets/fonts/Uni-Sans-Heavy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Java-Discord/JavaBot/HEAD/src/main/resources/assets/fonts/Uni-Sans-Heavy.ttf -------------------------------------------------------------------------------- /src/main/resources/help_guidelines/guidelines_image_url.txt: -------------------------------------------------------------------------------- 1 | https://cdn.discordapp.com/attachments/744899463591624815/997819764758028288/format-your-code.gif -------------------------------------------------------------------------------- /src/test/java/net/discordjug/javabot/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains all tests for the JavaBot. 3 | */ 4 | 5 | package net.discordjug.javabot; -------------------------------------------------------------------------------- /src/main/resources/assets/images/LeaderboardUserCard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Java-Discord/JavaBot/HEAD/src/main/resources/assets/images/LeaderboardUserCard.png -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.address=127.0.0.1 2 | server.port=9000 3 | tomcat.ajp.port=9001 4 | tomcat.ajp.enabled=true 5 | tomcat.ajp.address=127.0.0.1 -------------------------------------------------------------------------------- /src/main/resources/assets/images/QuestionOfTheWeekHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Java-Discord/JavaBot/HEAD/src/main/resources/assets/images/QuestionOfTheWeekHeader.png -------------------------------------------------------------------------------- /src/main/resources/database/migrations/09-15-2024_custom_vcs.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE custom_vc ( 2 | channel_id BIGINT NOT NULL PRIMARY KEY, 3 | owner_id BIGINT NOT NULL 4 | ) 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | #https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 2 | * @Java-Discord/Reviewers 3 | -------------------------------------------------------------------------------- /src/main/resources/database/migrations/11-11-2024_store_qotw_champion.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE qotw_champion ( 2 | guild_id BIGINT NOT NULL, 3 | user_id BIGINT NOT NULL, 4 | PRIMARY KEY(guild_id, user_id) 5 | ) -------------------------------------------------------------------------------- /src/main/resources/database/migrations/08-15-2023_staff_activity.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE staff_activity_messages ( 2 | guild_id BIGINT NOT NULL, 3 | user_id BIGINT NOT NULL, 4 | message_id BIGINT NOT NULL, 5 | PRIMARY KEY(guild_id, user_id) 6 | ) -------------------------------------------------------------------------------- /src/main/resources/database/migrations/02-13-2023_message_cache_links.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE message_cache_attachments ( 2 | message_id BIGINT NOT NULL, 3 | attachment_index INT NOT NULL, 4 | link VARCHAR(255), 5 | PRIMARY KEY(message_id, attachment_index) 6 | ) 7 | -------------------------------------------------------------------------------- /src/main/resources/database/migrations/12-17-2022_qotw_point_timestamps.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE qotw_points ADD obtained_at DATE NOT NULL DEFAULT CURRENT_TIMESTAMP(0); 2 | ALTER TABLE qotw_points DROP PRIMARY KEY; 3 | ALTER TABLE qotw_points ADD PRIMARY KEY (user_id, obtained_at); -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all the various systems that the bot manages. Generally, each system 3 | * is entirely contained within the confines of a package within this one. 4 | */ 5 | 6 | package net.discordjug.javabot.systems; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | /bot.props 5 | /config.json 6 | /config 7 | /purgeArchives 8 | /logs 9 | /db 10 | 11 | # Eclipse settings 12 | .classpath 13 | .project 14 | .settings/ 15 | bin/ 16 | 17 | # H2 Database 18 | *.mv.db 19 | *.trace.db 20 | *.lock.db 21 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/config/InvalidPropertyValueException.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.config; 2 | 3 | /** 4 | * Exception that is thrown when a invalid config property is requested. 5 | */ 6 | public class InvalidPropertyValueException extends Exception { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_preferences/model/StringPreference.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_preferences.model; 2 | 3 | /** 4 | * Represents a {@link Preference} of the {@link String} type. 5 | */ 6 | public final class StringPreference implements PreferenceType {} 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/model/QOTWAccount.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * Simple data class that represents a single QOTW Account. 7 | */ 8 | @Data 9 | public class QOTWAccount { 10 | private long userId; 11 | private long points; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains all components pertaining to the Question of the Week 3 | * system, which involves storing a queue of questions in the database, from 4 | * which a new question is pulled from each week. 5 | */ 6 | 7 | package net.discordjug.javabot.systems.qotw; -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/StatementModifier.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * Interface to modify statements. 8 | */ 9 | @FunctionalInterface 10 | public interface StatementModifier { 11 | void modify(PreparedStatement s) throws SQLException; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/Pair.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | /** 4 | * A simple pair that contains to values. 5 | * 6 | * @param first The first value. 7 | * @param second The second value. 8 | * @param The type of the first value. 9 | * @param The type of the second value. 10 | */ 11 | public record Pair(F first, S second) { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/data/UserData.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes.data; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * Abstract class which contains basic user values. 7 | */ 8 | @Data 9 | public abstract class UserData { 10 | private long userId; 11 | private String userName; 12 | private String discriminator; 13 | private String effectiveAvatarUrl; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/ConnectionConsumer.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db; 2 | 3 | import java.sql.Connection; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * Functional interface for defining operations that use a Connection. 8 | */ 9 | @FunctionalInterface 10 | public interface ConnectionConsumer { 11 | void consume(Connection con) throws SQLException; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/ResultSetMapper.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * Interface for mapping {@link ResultSet}s. 8 | * 9 | * @param The generic type. 10 | */ 11 | @FunctionalInterface 12 | public interface ResultSetMapper { 13 | T map(ResultSet rs) throws SQLException; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/exception/InternalServerException.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.exception; 2 | 3 | import org.springframework.beans.BeansException; 4 | 5 | /** 6 | * An exception which is thrown for invalid JDA entity ids. 7 | */ 8 | public class InternalServerException extends BeansException { 9 | 10 | public InternalServerException(String msg, Throwable cause) { 11 | super(msg, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/DaoConsumer.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db; 2 | 3 | import java.sql.SQLException; 4 | 5 | /** 6 | * Functional interface for defining operations that consume a specified data- 7 | * access object. 8 | * 9 | * @param The type of the data access object. 10 | */ 11 | @FunctionalInterface 12 | public interface DaoConsumer { 13 | void consume(T dao) throws SQLException; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/TransactionFunction.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db; 2 | 3 | import java.sql.Connection; 4 | 5 | /** 6 | * Interface that defines a function that executes using a transaction. 7 | * It is possible for an exception to be thrown, in which case the transaction 8 | * will attempt to roll back. 9 | */ 10 | public interface TransactionFunction { 11 | void execute(Connection c) throws Exception; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/starboard/model/StarboardEntry.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.starboard.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * Simple data class that represents a single Starboard Entry. 7 | */ 8 | @Data 9 | public class StarboardEntry { 10 | private long originalMessageId; 11 | private long guildId; 12 | private long channelId; 13 | private long authorId; 14 | private long starboardMessageId; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/ConnectionFunction.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db; 2 | 3 | import java.sql.Connection; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * Functional interface for a function which produces some object using a 8 | * connection. 9 | * 10 | * @param The generic type that is returned. 11 | */ 12 | @FunctionalInterface 13 | public interface ConnectionFunction { 14 | T apply(Connection c) throws SQLException; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_activity/StaffActivityType.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_activity; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * Types of recorded staff activities. 8 | */ 9 | @RequiredArgsConstructor 10 | public enum StaffActivityType { 11 | /** 12 | * The last message sent by the staff member. 13 | */ 14 | LAST_MESSAGE("Last message sent"); 15 | 16 | @Getter 17 | private final String title; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/help/model/HelpTransaction.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.help.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | /** 8 | * Data class that represents a single Help Transaction. 9 | */ 10 | @Data 11 | public class HelpTransaction { 12 | private long id; 13 | private long recipient; 14 | private LocalDateTime createdAt; 15 | private double weight; 16 | private int messageType; 17 | private long channelId = -1; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_preferences/model/UserPreference.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_preferences.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * Data class which represents a single user preference. 9 | */ 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class UserPreference { 14 | private long userId; 15 | private Preference preference; 16 | private String state; 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: Bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 1. **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | 2. **Steps to reproduce** 14 | Which Command? What exactly did you do, etc... 15 | 16 | 3. **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | 4. **Additional context** 20 | Add any other context about the problem here. 21 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/metrics/model/MetricsData.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes.metrics.model; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | /** 7 | * API-Data class which contains all necessary information about a guilds' 8 | * metrics. 9 | */ 10 | @Data 11 | @EqualsAndHashCode(callSuper = false) 12 | public class MetricsData { 13 | private long memberCount; 14 | private long onlineCount; 15 | private long weeklyMessages; 16 | private long activeMembers; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_activity/model/StaffActivityMessage.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_activity.model; 2 | 3 | /** 4 | * Represents metadata of a message where activity information of a staff member is stored. 5 | * @param guildId the ID of the guild 6 | * @param userId the ID of the staff member 7 | * @param messageId the ID of the message storing activity information about the staff member 8 | */ 9 | public record StaffActivityMessage(long guildId, long userId, long messageId) {} 10 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/exception/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | /** 6 | * Represents a single API-Error which holds the {@link HttpStatus}, a message and 7 | * an array of errors. 8 | * 9 | * @param status The {@link HttpStatus}. 10 | * @param message The errors' message. 11 | * @param errors An array of additional error notices. 12 | */ 13 | public record ErrorResponse(HttpStatus status, String message, String... errors) {} 14 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_preferences/model/PreferenceType.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_preferences.model; 2 | 3 | import net.dv8tion.jda.api.interactions.commands.Command; 4 | 5 | /** 6 | * Interface used by different Preference Types. This holds the default and allowed choices. 7 | */ 8 | public interface PreferenceType { 9 | default String[] getAllowedChoices() { 10 | return new String[0]; 11 | } 12 | 13 | default Command.Choice[] getDefaultChoices() { 14 | return new Command.Choice[0]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | RUN apk add --no-cache libsm libxrender libxext libxtst libxi gcompat ttf-dejavu 3 | 4 | COPY build/native/nativeCompile /work 5 | WORKDIR /work 6 | 7 | RUN chown 1000:1000 /work 8 | USER 1000 9 | ENV HOME=/work 10 | 11 | # https://github.com/openjdk/jdk/pull/20169 12 | # need to create fake JAVA_HOME 13 | RUN mkdir -p /tmp/JAVA_HOME/conf/fonts 14 | RUN mkdir /tmp/JAVA_HOME/lib 15 | 16 | 17 | VOLUME "/work/config" 18 | VOLUME "/work/logs" 19 | VOLUME "/work/db" 20 | VOLUME "/work/purgeArchives" 21 | ENTRYPOINT [ "./javabot", "-Djava.home=/tmp/JAVA_HOME" ] 22 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/CaffeineCache.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes; 2 | 3 | import com.github.benmanes.caffeine.cache.Cache; 4 | import lombok.Getter; 5 | 6 | /** 7 | * Simple parent class which enables all extending classes to have their own 8 | * {@link Cache}. 9 | * 10 | * @param The caches' key. 11 | * @param The caches' value. 12 | */ 13 | public abstract class CaffeineCache { 14 | 15 | @Getter 16 | private final Cache cache; 17 | 18 | protected CaffeineCache(Cache cache) { 19 | this.cache = cache; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/exception/InvalidEntityIdException.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.exception; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.lang.Nullable; 5 | 6 | /** 7 | * An exception which is thrown for invalid JDA entity ids. 8 | */ 9 | public class InvalidEntityIdException extends BeansException { 10 | @Nullable 11 | private final Class requiredEntity; 12 | 13 | public InvalidEntityIdException(Class requiredEntity, String msg) { 14 | super(msg); 15 | this.requiredEntity = requiredEntity; 16 | } 17 | 18 | public Class getRequiredEntity() { 19 | return requiredEntity; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/config/guild/ServerLockConfig.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.config.guild; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import net.discordjug.javabot.data.config.GuildConfigItem; 6 | 7 | /** 8 | * Configuration for the guild's serverlock system. 9 | */ 10 | @Data 11 | @EqualsAndHashCode(callSuper = true) 12 | public class ServerLockConfig extends GuildConfigItem { 13 | private String locked = "false"; 14 | private int minimumAccountAgeInDays = 7; 15 | private int lockThreshold = 5; 16 | private float minimumSecondsBetweenJoins = 1.0f; 17 | 18 | public boolean isLocked() { 19 | return Boolean.parseBoolean(this.locked); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: new feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_preferences/model/BooleanPreference.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_preferences.model; 2 | 3 | import net.dv8tion.jda.api.interactions.commands.Command; 4 | 5 | /** 6 | * Represents a {@link Preference} of the {@link Boolean} type. 7 | */ 8 | public final class BooleanPreference implements PreferenceType { 9 | @Override 10 | public String[] getAllowedChoices() { 11 | return new String[]{ 12 | "true", "false" 13 | }; 14 | } 15 | 16 | @Override 17 | public Command.Choice[] getDefaultChoices() { 18 | return new Command.Choice[]{ 19 | new Command.Choice("Enable", "true"), 20 | new Command.Choice("Disable", "false") 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | INFO 8 | 9 | 10 | %d{dd.MM.yyyy HH:mm:ss} %boldCyan(%-34.-34thread) %red(%10.10X{jda.shard}) %boldGreen(%-15.-15logger{0}) %highlight(%-6level) %msg%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/user_profile/model/UserProfileData.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes.user_profile.model; 2 | 3 | import lombok.Data; 4 | import net.discordjug.javabot.systems.moderation.warn.model.Warn; 5 | import net.discordjug.javabot.systems.qotw.model.QOTWAccount; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * API-Data class which contains all necessary information about a users' 11 | * profile. 12 | */ 13 | @Data 14 | public class UserProfileData { 15 | private long userId; 16 | private String userName; 17 | private String discriminator; 18 | private String effectiveAvatarUrl; 19 | private QOTWAccount qotwAccount; 20 | private HelpAccountData helpAccount; 21 | private List warns; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/Constants.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | /** 4 | * Utility class that contains some constant variables. 5 | */ 6 | public class Constants { 7 | 8 | /** 9 | * A static String referencing to the Java Discord's Website. 10 | */ 11 | public static final String WEBSITE_LINK = "https://discordjug.net"; 12 | 13 | /** 14 | * A static String referencing to the Java Discord's GitHub Repository. 15 | */ 16 | public static final String GITHUB_LINK = "https://github.com/Java-Discord/JavaBot/"; 17 | 18 | /** 19 | * The url which lets users join the discord. 20 | */ 21 | public static final String INVITE_URL = "https://join.discordjug.net"; 22 | 23 | private Constants() { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/UserUtils.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import net.dv8tion.jda.api.entities.User; 4 | 5 | /** 6 | * Utils regarding users. 7 | */ 8 | public class UserUtils { 9 | /** 10 | * Returns the tag of a discord user, in the format "[username]#[discriminator]", or just "[username]" if the new username system is used. 11 | * 12 | * @param user The {@link User} to get the tag of 13 | * 14 | * @return The formatted tag of the user 15 | */ 16 | public static String getUserTag(User user) { 17 | String name = user.getName(); 18 | String discrim = user.getDiscriminator(); 19 | if ("0000".equals(discrim)) { 20 | return name; 21 | } else { 22 | return name + "#" + discrim; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/config/guild/MetricsConfig.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.config.guild; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import net.discordjug.javabot.data.config.GuildConfigItem; 6 | import net.dv8tion.jda.api.entities.channel.concrete.Category; 7 | 8 | /** 9 | * Configuration for the guild's stats system. 10 | */ 11 | @Data 12 | @EqualsAndHashCode(callSuper = true) 13 | public class MetricsConfig extends GuildConfigItem { 14 | private long weeklyMessages = -1; 15 | private long activeMembers = -1; 16 | private long metricsCategoryId = 0; 17 | private String metricsMessageTemplate = ""; 18 | 19 | public Category getMetricsCategory() { 20 | return getGuild().getCategoryById(this.metricsCategoryId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/annotations/AutoDetectableComponentHandler.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * Annotation for component (non-slash command-interactions) handlers which should be registered automatically. 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | @Component 16 | public @interface AutoDetectableComponentHandler { 17 | /** 18 | * The names the handler should be called under. 19 | * Each name should be unique. 20 | * @return The names of the handler 21 | */ 22 | String[] value(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/annotations/PreRegisteredListener.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.annotations; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Marks JDA listeners to be registered before JDA is initialized. 11 | * 12 | * Only {@link net.dv8tion.jda.api.hooks.EventListener EventListener}s should be annoted with this annotation. 13 | * All listeners overriding {@link net.dv8tion.jda.api.hooks.EventListener#onReady(net.dv8tion.jda.api.events.session.ReadyEvent) onReady} should be annotated with this annotation. 14 | */ 15 | @Retention(RUNTIME) 16 | @Target(TYPE) 17 | public @interface PreRegisteredListener { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/config/GuildConfigItem.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.config; 2 | 3 | import lombok.Setter; 4 | import net.dv8tion.jda.api.entities.Guild; 5 | 6 | /** 7 | * Parent class for any guild-specific collection of configuration settings, 8 | * which exposes a link to the parent guild config, and thus, the guild, which 9 | * may be needed to obtain text channels. 10 | */ 11 | public abstract class GuildConfigItem { 12 | /** 13 | * A reference to the parent config for this item. 14 | */ 15 | @Setter 16 | protected transient GuildConfig guildConfig; 17 | 18 | /** 19 | * A shortcut to get the guild in the context of a specific config item. 20 | * 21 | * @return The guild that this item exists under. 22 | */ 23 | public Guild getGuild() { 24 | return this.guildConfig.getGuild(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/listener/filter/MessageContent.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.listener.filter; 2 | 3 | import net.dv8tion.jda.api.entities.Message; 4 | import net.dv8tion.jda.api.entities.MessageEmbed; 5 | import net.dv8tion.jda.api.events.message.MessageReceivedEvent; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * A class containing modifiable content of a message that has been received. 11 | * @param event The event associated with receiving the message 12 | * @param messageText The text associated with the message 13 | * @param attachments The attachments associated with the message 14 | * @param embeds The embeds associated with the message 15 | */ 16 | public record MessageContent(MessageReceivedEvent event, 17 | StringBuilder messageText, 18 | List attachments, 19 | List embeds) { 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/config/UnknownPropertyException.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.config; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * Exception class that handles unknown config properties. 7 | */ 8 | @Getter 9 | public class UnknownPropertyException extends Exception { 10 | private final String propertyName; 11 | private final Object parentClass; 12 | 13 | /** 14 | * Exception that is thrown when a config property does not exist or could not be found. 15 | * 16 | * @param propertyName The properties' name. 17 | * @param parentClass The parent class. 18 | */ 19 | public UnknownPropertyException(String propertyName, Class parentClass) { 20 | super(String.format("No property named \"%s\" could be found for class %s.", propertyName, parentClass)); 21 | this.propertyName = propertyName; 22 | this.parentClass = parentClass; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/ColorUtils.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import java.awt.*; 4 | import java.util.Random; 5 | import java.util.concurrent.ThreadLocalRandom; 6 | 7 | /** 8 | * Utility class for generating colors. 9 | */ 10 | public final class ColorUtils { 11 | private ColorUtils() { 12 | } 13 | 14 | /** 15 | * Generates a random pastel color. 16 | * 17 | * @return A random pastel color. 18 | */ 19 | public static Color randomPastel() { 20 | Random rand = ThreadLocalRandom.current(); 21 | float hue = rand.nextFloat(); 22 | float saturation = (rand.nextInt(2000) + 1000) / 10000f; 23 | float luminance = 0.9f; 24 | return Color.getHSBColor(hue, saturation, luminance); 25 | } 26 | 27 | public static String toString(Color color) { 28 | if (color == null) return null; 29 | return "#" + Integer.toHexString(color.getRGB()).substring(2).toUpperCase(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/listener/filter/MessageModificationStatus.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.listener.filter; 2 | 3 | /** 4 | * This enum describes the result of a {@link MessageFilter} execution and what should be done with the message. 5 | */ 6 | public enum MessageModificationStatus { 7 | /** 8 | * The message representation has been modified requiring the message to be deleted and re-sent. 9 | * 10 | * Further filters will be executed. 11 | */ 12 | MODIFIED, 13 | /** 14 | * The message representation has not been modified and the filter does not require the message to be deleted and re-sent. 15 | * 16 | * If another filter returns {@link #MODIFIED}, the message is still deleted and re-sent. 17 | * Further filters will be executed. 18 | */ 19 | NOT_MODIFIED, 20 | /** 21 | * Indicates that no further filters should be executed on the message and that the message should not be deleted and re-sent by the filter handling logic. 22 | */ 23 | STOP_PROCESSING 24 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/CorsFilter.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes; 2 | 3 | import java.io.IOException; 4 | 5 | import jakarta.servlet.FilterChain; 6 | import jakarta.servlet.ServletException; 7 | import jakarta.servlet.http.HttpServletRequest; 8 | import jakarta.servlet.http.HttpServletResponse; 9 | 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.filter.OncePerRequestFilter; 12 | 13 | /** 14 | * This filter enables CORS for all endpoints. 15 | */ 16 | @Component 17 | public class CorsFilter extends OncePerRequestFilter { 18 | 19 | @Override 20 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 21 | throws ServletException, IOException { 22 | response.addHeader("Access-Control-Allow-Origin", "*"); 23 | response.addHeader("Access-Control-Allow-Methods", "POST, PUT, DELETE, GET, OPTIONS"); 24 | filterChain.doFilter(request, response); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/custom_vc/commands/CustomVCControlCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.custom_vc.commands; 2 | 3 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 4 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 5 | 6 | /** 7 | * Command for managing custom voice channels. 8 | */ 9 | public class CustomVCControlCommand extends SlashCommand { 10 | /** 11 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 12 | * @param addMemberSubcommand /vc-control add-member 13 | * @param removeMemberSubcommand /vc-control remove-member 14 | */ 15 | public CustomVCControlCommand(CustomVCAddMemberSubcommand addMemberSubcommand, CustomVCRemoveMemberSubcommand removeMemberSubcommand) { 16 | setCommandData(Commands.slash("vc-control", "Manages custom voice channels") 17 | .setGuildOnly(true) 18 | ); 19 | addSubcommands(addMemberSubcommand, removeMemberSubcommand); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/listener/ShareKnowledgeVoteListener.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.listener; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | 5 | import net.discordjug.javabot.data.config.BotConfig; 6 | import net.dv8tion.jda.api.entities.Guild; 7 | import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; 8 | 9 | /** 10 | * Listens for messages and reactions in #share-knowledge. 11 | * Automatically deletes messages below a certain score. 12 | */ 13 | public class ShareKnowledgeVoteListener extends ForumPostVoteListener { 14 | public ShareKnowledgeVoteListener(BotConfig botConfig, ExecutorService asyncPool) { 15 | super(botConfig, asyncPool); 16 | } 17 | 18 | @Override 19 | protected ForumChannel getChannel(Guild guild) { 20 | return botConfig.get(guild).getModerationConfig().getShareKnowledgeChannel(); 21 | } 22 | 23 | @Override 24 | protected int getMessageDeleteVoteThreshold(Guild guild) { 25 | return botConfig.get(guild).getModerationConfig().getShareKnowledgeMessageDeleteThreshold(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/config/guild/StarboardConfig.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.config.guild; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import net.discordjug.javabot.data.config.GuildConfigItem; 6 | import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; 7 | import net.dv8tion.jda.api.entities.emoji.Emoji; 8 | import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Configuration for the guild's starboard system. 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = true) 18 | public class StarboardConfig extends GuildConfigItem { 19 | private long starboardChannelId; 20 | private int reactionThreshold; 21 | private List emojiUnicodes = new ArrayList<>(); 22 | 23 | public TextChannel getStarboardChannel() { 24 | return this.getGuild().getTextChannelById(this.starboardChannelId); 25 | } 26 | 27 | public List getEmojis() { 28 | return emojiUnicodes.stream().map(Emoji::fromUnicode).toList(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/help/checks/ChannelSemanticData.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.help.checks; 2 | 3 | import net.dv8tion.jda.api.entities.Message; 4 | import net.dv8tion.jda.api.entities.User; 5 | 6 | import javax.annotation.Nullable; 7 | import java.time.Duration; 8 | import java.util.List; 9 | 10 | /** 11 | * Simple data class that represents a single help channel. 12 | * 13 | * @param initialMessage The help channel's initial message. 14 | * @param timeSinceFirstMessage The time since the initial message. 15 | * @param nonOwnerParticipants All members that participated, excluding the owner. 16 | * @param botMessages All messages that the bot sent. 17 | */ 18 | public record ChannelSemanticData( 19 | @Nullable Message initialMessage, 20 | Duration timeSinceFirstMessage, 21 | List nonOwnerParticipants, 22 | List botMessages 23 | ) { 24 | public boolean containsBotMessageContent(String content) { 25 | return botMessages.stream() 26 | .anyMatch(m -> m.getContentRaw().contains(content)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_preferences/commands/PreferencesCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_preferences.commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 5 | 6 | /** 7 | *

This class represents the /preferences command.

8 | */ 9 | public class PreferencesCommand extends SlashCommand { 10 | /** 11 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 12 | * @param preferencesListSubcommand /preferences list 13 | * @param preferencesSetSubcommand /preferences set 14 | */ 15 | public PreferencesCommand(PreferencesListSubcommand preferencesListSubcommand, PreferencesSetSubcommand preferencesSetSubcommand) { 16 | setCommandData(Commands.slash("preferences", "Contains commands for managing user preferences.") 17 | .setGuildOnly(true) 18 | ); 19 | addSubcommands(preferencesListSubcommand, preferencesSetSubcommand); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/ExceptionLogger.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import io.sentry.Sentry; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | /** 7 | * Utility class which unifies the way exceptions are logged. 8 | */ 9 | @Slf4j 10 | public final class ExceptionLogger { 11 | private ExceptionLogger() { 12 | } 13 | 14 | /** 15 | * Captures a single {@link Throwable}. 16 | * 17 | * @param t The {@link Throwable} to log. 18 | * @param name The origin's name. 19 | */ 20 | public static void capture(Throwable t, String name) { 21 | Sentry.captureException(t); 22 | log.error("I've encountered an {} in {}: {}", t.getClass().getSimpleName(), name, t.getMessage()); 23 | t.printStackTrace(); 24 | } 25 | 26 | /** 27 | * Captures a single {@link Throwable}. 28 | * 29 | * @param t The {@link Throwable} to log. 30 | */ 31 | public static void capture(Throwable t) { 32 | Sentry.captureException(t); 33 | log.error("I've encountered an {}: {}", t.getClass().getSimpleName(), t.getMessage()); 34 | t.printStackTrace(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/timeout/TimeoutCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.timeout; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.systems.moderation.CommandModerationPermissions; 5 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 6 | 7 | /** 8 | * Handler class for all timeout specific commands. 9 | */ 10 | public class TimeoutCommand extends SlashCommand implements CommandModerationPermissions { 11 | /** 12 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 13 | * @param addTimeoutSubcommand /timeout add 14 | * @param removeTimeoutSubcommand /timeout remove 15 | */ 16 | public TimeoutCommand(AddTimeoutSubcommand addTimeoutSubcommand, RemoveTimeoutSubcommand removeTimeoutSubcommand) { 17 | setModerationSlashCommandData(Commands.slash("timeout", "Commands for managing member timeouts.")); 18 | addSubcommands(addTimeoutSubcommand, removeTimeoutSubcommand); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/tags/model/CustomTag.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.tags.model; 2 | 3 | import lombok.Data; 4 | import net.discordjug.javabot.systems.staff_commands.tags.CustomTagManager; 5 | import net.dv8tion.jda.api.EmbedBuilder; 6 | import net.dv8tion.jda.api.entities.MessageEmbed; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | /** 11 | * A data class that represents a single Custom Command. 12 | */ 13 | @Data 14 | public class CustomTag { 15 | private long id; 16 | private long guildId; 17 | private long createdBy; 18 | private String name; 19 | private String response; 20 | private boolean reply; 21 | private boolean embed; 22 | 23 | public void setName(@NotNull String name) { 24 | this.name = CustomTagManager.cleanString(name); 25 | } 26 | 27 | /** 28 | * Converts this {@link CustomTag}'s response into a {@link MessageEmbed}. 29 | * 30 | * @return The built {@link MessageEmbed}. 31 | */ 32 | public MessageEmbed toEmbed() { 33 | return new EmbedBuilder().setDescription(response).build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/listener/JobChannelVoteListener.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.listener; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | 5 | import net.discordjug.javabot.data.config.BotConfig; 6 | import net.dv8tion.jda.api.entities.Guild; 7 | import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; 8 | 9 | /** 10 | * Listens for reactions in #looking-for-programmer. 11 | * Automatically deletes messages below a certain score. 12 | */ 13 | public class JobChannelVoteListener extends ForumPostVoteListener { 14 | 15 | public JobChannelVoteListener(BotConfig botConfig, ExecutorService asyncPool) { 16 | super(botConfig, asyncPool); 17 | } 18 | 19 | @Override 20 | protected ForumChannel getChannel(Guild guild) { 21 | return botConfig.get(guild).getModerationConfig().getJobChannel(); 22 | } 23 | 24 | @Override 25 | protected int getMessageDeleteVoteThreshold(Guild guild) { 26 | return botConfig.get(guild).getModerationConfig().getJobChannelMessageDeleteThreshold(); 27 | } 28 | 29 | @Override 30 | protected boolean shouldAddInitialEmotes(Guild guild) { 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/listener/filter/MessageFilter.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.listener.filter; 2 | 3 | /** 4 | * This interface is implemented by all message filters. 5 | * 6 | * The {@link MessageContent} is processed by every class implementing {@link MessageFilter} 7 | * unless one of the filters returns {@link MessageModificationStatus#STOP_PROCESSING} which stops further filters from executing. 8 | */ 9 | public interface MessageFilter { 10 | 11 | /** 12 | * When a message is received, it is processed by the registered filters. 13 | * 14 | * @param content The content of the new message that will be reposted instead of the received message 15 | * if at least one filter returns {@link MessageModificationStatus#MODIFIED} 16 | * and no filter returns {@link MessageModificationStatus#STOP_PROCESSING}. 17 | * This {@link MessageContent} is built up incrementally by the filters. 18 | * @return the appropriate {@link MessageModificationStatus} based on the filter's processing. 19 | * @see MessageFilterHandler 20 | */ 21 | MessageModificationStatus processMessage(MessageContent content); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/MessageUtils.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import net.dv8tion.jda.api.entities.Message; 4 | import net.dv8tion.jda.api.entities.MessageReference; 5 | import net.dv8tion.jda.api.entities.messages.MessageSnapshot; 6 | 7 | /** 8 | * Utility class for JDA messages. 9 | */ 10 | public class MessageUtils { 11 | 12 | private MessageUtils() { 13 | //prevent instantiation 14 | } 15 | 16 | /** 17 | * Gets the actual content of a message. 18 | * In case of forwarded messages, this gets the content of the forwarded message. 19 | * @param msg the message to check 20 | * @return the content of the passed message 21 | */ 22 | public static String getMessageContent(Message msg) { 23 | //see https://github.com/discord-jda/JDA/releases/tag/v5.1.2 24 | MessageReference messageReference = msg.getMessageReference(); 25 | if (messageReference != null && messageReference.getType() == MessageReference.MessageReferenceType.FORWARD) { 26 | MessageSnapshot snapshot = msg.getMessageSnapshots().get(0); 27 | if (snapshot != null) { 28 | return snapshot.getContentRaw(); 29 | } 30 | } 31 | return msg.getContentRaw(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/tags/commands/TagsCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.tags.commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | 5 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 6 | 7 | /** 8 | * Represents the `/tag` command. This holds commands for interacting with "Custom Tags". 9 | */ 10 | public class TagsCommand extends SlashCommand { 11 | /** 12 | * This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and 13 | * adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.Subcommand}s. 14 | * @param tagViewSubcommand /tag view 15 | * @param tagListSubcommand /tag list 16 | * @param tagSearchSubcommand /tag search 17 | */ 18 | public TagsCommand(TagViewSubcommand tagViewSubcommand, TagListSubcommand tagListSubcommand, TagSearchSubcommand tagSearchSubcommand) { 19 | setCommandData(Commands.slash("tag", "Commands for interacting with Custom Tags.") 20 | .setGuildOnly(true) 21 | ); 22 | addSubcommands(tagViewSubcommand, tagListSubcommand, tagSearchSubcommand); 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/warn/model/Warn.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.warn.model; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | /** 10 | * Entity representing an issued warning for a user. 11 | */ 12 | @Data 13 | @NoArgsConstructor 14 | public class Warn { 15 | private Long id; 16 | private long userId; 17 | private long warnedBy; 18 | private LocalDateTime createdAt; 19 | private String severity; 20 | private int severityWeight; 21 | private String reason; 22 | private boolean discarded; 23 | 24 | /** 25 | * Constructs a new warning. 26 | * 27 | * @param userId The id of the user being warned. 28 | * @param warnedBy The id of the user who's warning them. 29 | * @param severity The severity of the warning. 30 | * @param reason The reason for the warning. 31 | */ 32 | public Warn(long userId, long warnedBy, @NotNull WarnSeverity severity, String reason) { 33 | this.userId = userId; 34 | this.warnedBy = warnedBy; 35 | this.severity = severity.name(); 36 | this.severityWeight = severity.getWeight(); 37 | this.reason = reason; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionStatus.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.submissions; 2 | 3 | /** 4 | * Represents a submissions' status. 5 | */ 6 | public enum SubmissionStatus { 7 | /** 8 | * The submission got accepted and was among the best answers for the current week. 9 | */ 10 | ACCEPT_BEST("accepted (best answer)"), 11 | /** 12 | * The submission got accepted but was not among the best answers for the current week. 13 | */ 14 | ACCEPT("accepted"), 15 | /** 16 | * The submission got declined as it was simply wrong. 17 | */ 18 | DECLINE_WRONG_ANSWER("declined (wrong answer)"), 19 | /** 20 | * The submission got declined as it was too short compared to other submissions. 21 | */ 22 | DECLINE_TOO_SHORT("declined (too short)"), 23 | /** 24 | * The submission got declined as it was simply empty. 25 | */ 26 | DECLINE_EMPTY("declined (empty)"), 27 | /** 28 | * The submission got declined as it was simply empty. 29 | */ 30 | DECLINE_PLAGIARISM("declined (plagiarism/AI)"); 31 | 32 | private final String verb; 33 | 34 | SubmissionStatus(String verb) { 35 | this.verb = verb; 36 | } 37 | 38 | public String getVerb() { 39 | return verb; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/CommandModerationPermissions.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation; 2 | 3 | import net.dv8tion.jda.api.Permission; 4 | import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; 5 | import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | /** 9 | * Interface which adds `setModerationSlashCommandData`. This method simply uses the provided {@link SlashCommandData} 10 | * and forces it to be (1.) guild only and (2.) only enabled for users with the {@link Permission#MODERATE_MEMBERS} 11 | * permission. 12 | */ 13 | public interface CommandModerationPermissions { 14 | /** 15 | * Takes the given {@link SlashCommandData} and forces it to be 16 | * guild only enabled for users with the {@link Permission#MODERATE_MEMBERS} 17 | * permission. 18 | * 19 | * @param data The {@link SlashCommandData}. 20 | */ 21 | default void setModerationSlashCommandData(@NotNull SlashCommandData data) { 22 | setCommandData(data.setGuildOnly(true) 23 | .setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MODERATE_MEMBERS)) 24 | ); 25 | } 26 | 27 | void setCommandData(SlashCommandData data); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/PingCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.util.Responses; 5 | import net.dv8tion.jda.api.EmbedBuilder; 6 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 7 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | /** 12 | *

This class represents the /ping command.

13 | */ 14 | public class PingCommand extends SlashCommand { 15 | /** 16 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 17 | */ 18 | public PingCommand() { 19 | setCommandData(Commands.slash("ping", "Shows the bot's gateway ping.") 20 | .setGuildOnly(true) 21 | ); 22 | } 23 | 24 | @Override 25 | public void execute(@NotNull SlashCommandInteractionEvent event) { 26 | event.replyEmbeds(new EmbedBuilder() 27 | .setAuthor(event.getJDA().getGatewayPing() + "ms", null, event.getJDA().getSelfUser().getAvatarUrl()) 28 | .setColor(Responses.Type.DEFAULT.getColor()) 29 | .build() 30 | ).queue(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/leaderboard/LeaderboardCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands.leaderboard; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 5 | 6 | /** 7 | * Represents the `/leaderboard` command. This holds commands viewing all the server's different leaderboards. 8 | */ 9 | public class LeaderboardCommand extends SlashCommand { 10 | /** 11 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 12 | * @param qotwLeaderboardSubcommand /leaderboard qotw 13 | * @param thanksLeaderboardSubcommand /leaderboard thanks 14 | * @param experienceLeaderboardSubcommand /leaderboard help-experience 15 | */ 16 | public LeaderboardCommand(QOTWLeaderboardSubcommand qotwLeaderboardSubcommand, ThanksLeaderboardSubcommand thanksLeaderboardSubcommand, ExperienceLeaderboardSubcommand experienceLeaderboardSubcommand) { 17 | setCommandData(Commands.slash("leaderboard", "Command for all leaderboards.") 18 | .setGuildOnly(true) 19 | ); 20 | addSubcommands(qotwLeaderboardSubcommand, thanksLeaderboardSubcommand, experienceLeaderboardSubcommand); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/QueryBuilder.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import java.sql.Connection; 4 | import java.sql.PreparedStatement; 5 | import java.sql.SQLException; 6 | import java.sql.Timestamp; 7 | import java.time.LocalDateTime; 8 | 9 | /** 10 | * Builds SQL Queries. 11 | */ 12 | public class QueryBuilder { 13 | private final PreparedStatement stmt; 14 | private int parameterIndex = 1; 15 | 16 | public QueryBuilder(Connection con, String sql) throws SQLException { 17 | this.stmt = con.prepareStatement(sql); 18 | } 19 | 20 | public QueryBuilder setLong(long n) throws SQLException { 21 | this.stmt.setLong(this.parameterIndex++, n); 22 | return this; 23 | } 24 | 25 | public QueryBuilder setString(String s) throws SQLException { 26 | this.stmt.setString(this.parameterIndex++, s); 27 | return this; 28 | } 29 | 30 | public QueryBuilder setTimestamp(LocalDateTime timestamp) throws SQLException { 31 | this.stmt.setTimestamp(this.parameterIndex++, Timestamp.valueOf(timestamp)); 32 | return this; 33 | } 34 | 35 | public QueryBuilder setBoolean(boolean b) throws SQLException { 36 | this.stmt.setBoolean(this.parameterIndex++, b); 37 | return this; 38 | } 39 | 40 | public PreparedStatement build() { 41 | return this.stmt; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/config/guild/MessageCacheConfig.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.config.guild; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import net.discordjug.javabot.data.config.GuildConfigItem; 6 | import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Configuration for the bot's message cache. 12 | */ 13 | @Data 14 | @EqualsAndHashCode(callSuper = true) 15 | public class MessageCacheConfig extends GuildConfigItem { 16 | /** 17 | * The amount of message that can be cached at once. 18 | */ 19 | private int maxCachedMessages = 1000; 20 | 21 | /** 22 | * ID of the Message Cache log channel. 23 | */ 24 | private long messageCacheLogChannelId = 0; 25 | 26 | /** 27 | * The amount of messages after which the DB is synchronized with the local cache. 28 | */ 29 | private int messageSynchronizationInterval = 50; 30 | 31 | /** 32 | * Channels the Bot should ignore. 33 | */ 34 | private List excludedChannels = List.of(); 35 | 36 | /** 37 | * Users the Bot should ignore. 38 | */ 39 | private List excludedUsers = List.of(); 40 | 41 | public TextChannel getMessageCacheLogChannel() { 42 | return this.getGuild().getTextChannelById(this.messageCacheLogChannelId); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/UptimeCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.util.Responses; 5 | import net.discordjug.javabot.util.StringUtils; 6 | import net.dv8tion.jda.api.EmbedBuilder; 7 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 8 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 9 | 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | /** 13 | *

This class represents the /uptime command.

14 | */ 15 | public class UptimeCommand extends SlashCommand { 16 | /** 17 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 18 | */ 19 | public UptimeCommand() { 20 | setCommandData(Commands.slash("uptime", "Shows the bot's current uptime.") 21 | .setGuildOnly(true) 22 | ); 23 | } 24 | 25 | @Override 26 | public void execute(@NotNull SlashCommandInteractionEvent event) { 27 | event.replyEmbeds(new EmbedBuilder() 28 | .setColor(Responses.Type.DEFAULT.getColor()) 29 | .setAuthor(StringUtils.formatUptime(), null, event.getJDA().getSelfUser().getAvatarUrl()).build() 30 | ).queue(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/help/commands/HelpCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.help.commands; 2 | 3 | import net.discordjug.javabot.systems.help.commands.notify.HelpPingSubcommand; 4 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 5 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 6 | 7 | /** 8 | * Represents the `/help` command. This holds commands related to the help system. 9 | */ 10 | public class HelpCommand extends SlashCommand { 11 | /** 12 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 13 | * @param helpAccountSubcommand /help account 14 | * @param helpPingSubcommand /help ping 15 | * @param helpGuidelinesSubcommand /help guidelines 16 | * @param helpStatisticsSubcommand /help stats 17 | */ 18 | public HelpCommand(HelpAccountSubcommand helpAccountSubcommand, HelpPingSubcommand helpPingSubcommand, HelpGuidelinesSubcommand helpGuidelinesSubcommand, HelpStatisticsSubcommand helpStatisticsSubcommand) { 19 | setCommandData(Commands.slash("help", "Commands related to the help system.") 20 | .setGuildOnly(true) 21 | ); 22 | addSubcommands(helpAccountSubcommand, helpPingSubcommand, helpGuidelinesSubcommand, helpStatisticsSubcommand); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/help/checks/ChannelSemanticCheck.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.help.checks; 2 | 3 | import net.dv8tion.jda.api.entities.Message; 4 | import net.dv8tion.jda.api.entities.User; 5 | import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; 6 | import net.dv8tion.jda.api.requests.RestAction; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Defines an analysis that can be performed on a list of messages and semantic 12 | * data obtained from a reserved help channel, possibly in order to provide 13 | * contextual help or guidance to the owner of the channel. 14 | */ 15 | public interface ChannelSemanticCheck { 16 | /** 17 | * Performs a check on the given data. 18 | * 19 | * @param channel The reserved help channel. 20 | * @param owner The user who reserved the help channel. 21 | * @param messages The list of messages sent in the channel since the user 22 | * reserved it, ordered from newest to oldest. 23 | * @param semanticData Extra semantic data that may be useful in determining 24 | * when to do things. 25 | * @return A rest action that completes when this check is done. 26 | */ 27 | RestAction doCheck(TextChannel channel, User owner, List messages, ChannelSemanticData semanticData); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/message_cache/model/CachedMessage.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db.message_cache.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import lombok.Data; 7 | import net.discordjug.javabot.util.MessageUtils; 8 | import net.dv8tion.jda.api.entities.Message; 9 | import net.dv8tion.jda.api.entities.Message.Attachment; 10 | 11 | /** 12 | * Represents a cached Message. 13 | */ 14 | @Data 15 | public class CachedMessage { 16 | private long messageId; 17 | private long authorId; 18 | private String messageContent; 19 | private List attachments=new ArrayList<>(); 20 | 21 | /** 22 | * Converts a {@link Message} object to a {@link CachedMessage}. 23 | * 24 | * @param message The {@link Message} to convert. 25 | * @return The built {@link CachedMessage}. 26 | */ 27 | public static CachedMessage of(Message message) { 28 | CachedMessage cachedMessage = new CachedMessage(); 29 | cachedMessage.setMessageId(message.getIdLong()); 30 | cachedMessage.setAuthorId(message.getAuthor().getIdLong()); 31 | cachedMessage.setMessageContent(MessageUtils.getMessageContent(message).trim()); 32 | cachedMessage.attachments = message 33 | .getAttachments() 34 | .stream() 35 | .map(Attachment::getUrl) 36 | .toList(); 37 | return cachedMessage; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/MigrationUtils.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.net.URL; 7 | import java.nio.file.*; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import net.discordjug.javabot.data.h2db.commands.MigrationsListSubcommand; 12 | 13 | /** 14 | * Utility class that handles SQL Migrations. 15 | */ 16 | public class MigrationUtils { 17 | 18 | private MigrationUtils() { 19 | } 20 | 21 | /** 22 | * Tries to get the Migrations Directories' Path. 23 | * 24 | * @return The Migrations Directories' Path. 25 | * @throws URISyntaxException If an error occurs. 26 | * @throws IOException If an error occurs. 27 | */ 28 | public static Path getMigrationsDirectory() throws URISyntaxException, IOException { 29 | URL resource = MigrationsListSubcommand.class.getResource("/database/migrations/"); 30 | if (resource == null) throw new IOException("Missing resource /migrations/"); 31 | URI uri = resource.toURI(); 32 | try { 33 | return Path.of(uri); 34 | } catch (FileSystemNotFoundException e) { 35 | Map env = new HashMap<>(); 36 | FileSystem dir = FileSystems.newFileSystem(uri, env); 37 | return dir.getPath("/database/migrations/"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/server_lock/ServerLockCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.server_lock; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.systems.moderation.CommandModerationPermissions; 5 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 6 | 7 | /** 8 | * Represents the `/serverlock-admin` command. This holds administrative commands for managing the server lock functionality. 9 | */ 10 | public class ServerLockCommand extends SlashCommand implements CommandModerationPermissions { 11 | /** 12 | * This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and 13 | * adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.Subcommand}s. 14 | * @param setLockStatusSubcommand /serverlock-admin set-status 15 | * @param checkLockStatusSubcommand /serverlock-admin check-status 16 | */ 17 | public ServerLockCommand(SetLockStatusSubcommand setLockStatusSubcommand, CheckLockStatusSubcommand checkLockStatusSubcommand) { 18 | setModerationSlashCommandData(Commands.slash("serverlock-admin", "Administrative commands for managing the server lock functionality.")); 19 | addSubcommands(setLockStatusSubcommand, checkLockStatusSubcommand); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/model/QOTWSubmission.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.model; 2 | 3 | import lombok.Data; 4 | import net.discordjug.javabot.util.ExceptionLogger; 5 | import net.dv8tion.jda.api.entities.User; 6 | import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; 7 | 8 | import java.util.function.Consumer; 9 | 10 | /** 11 | * Simple data class that represents a single {@link ThreadChannel submission} and the corresponding 12 | * {@link User author}. 13 | */ 14 | @Data 15 | public class QOTWSubmission { 16 | private final ThreadChannel thread; 17 | private User author; 18 | 19 | public boolean hasAuthor() { 20 | return author != null; 21 | } 22 | 23 | /** 24 | * Attempts to retrieve the thread's actual author. Since the bot is creating submission threads, we can't use 25 | * {@link ThreadChannel#getOwnerThreadMember()}, so we just filter all bot-users instead. 26 | * 27 | * @param onSuccess The success-{@link Consumer} for this operation. 28 | */ 29 | public void retrieveAuthor(Consumer onSuccess) { 30 | if (author != null) { 31 | onSuccess.accept(author); 32 | return; 33 | } 34 | thread 35 | .getJDA() 36 | .retrieveUserById(thread.getName().split(" — ")[1]) 37 | .queue(onSuccess, e -> ExceptionLogger.capture(e, QOTWSubmission.class.getSimpleName())); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands.format_code; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; 4 | import net.discordjug.javabot.util.StringUtils; 5 | import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; 6 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | *

This class represents the "Format Code" Message Context command.

14 | */ 15 | public class FormatCodeMessageContext extends ContextCommand.Message { 16 | /** 17 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.CommandData}. 18 | */ 19 | public FormatCodeMessageContext() { 20 | setCommandData(Commands.message("Format Code") 21 | .setGuildOnly(true) 22 | ); 23 | } 24 | 25 | @Override 26 | public void execute(@NotNull MessageContextInteractionEvent event) { 27 | event.replyFormat("```java\n%s\n```", StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw())) 28 | .setAllowedMentions(List.of()) 29 | .setComponents(FormatCodeCommand.buildActionRow(event.getTarget(), event.getUser().getIdLong())) 30 | .queue(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/TableProperty.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db; 2 | 3 | import lombok.Data; 4 | import org.h2.api.H2Type; 5 | import org.jetbrains.annotations.Contract; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.function.BiConsumer; 9 | import java.util.function.Function; 10 | 11 | /** 12 | * This class is used inside {@link DatabaseRepository}s. It describes a single 13 | * column and combines it which their respective Model Class, {@link T}. 14 | * 15 | * @param The Model class. 16 | */ 17 | @Data 18 | public final class TableProperty { 19 | private final String propertyName; 20 | private final H2Type h2Type; 21 | private final BiConsumer consumer; 22 | private final Function function; 23 | private final boolean key; 24 | 25 | @Contract("_, _, _, _ -> new") 26 | public static @NotNull TableProperty of(String propertyName, H2Type h2Type, BiConsumer consumer, Function function) { 27 | return new TableProperty<>(propertyName, h2Type, consumer, function, false); 28 | } 29 | 30 | @Contract("_, _, _, _, _ -> new") 31 | public static @NotNull TableProperty of(String propertyName, H2Type h2Type, BiConsumer consumer, Function function, boolean isKey) { 32 | return new TableProperty<>(propertyName, h2Type, consumer, function, isKey); 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/custom_vc/commands/CustomVCAddMemberSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.custom_vc.commands; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.systems.custom_vc.CustomVCRepository; 5 | import net.dv8tion.jda.api.Permission; 6 | import net.dv8tion.jda.api.entities.Member; 7 | import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; 8 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 9 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 10 | 11 | /** 12 | * Adds a member to a custom voice channel. 13 | */ 14 | public class CustomVCAddMemberSubcommand extends CustomVCChangeMembersSubcommand { 15 | 16 | public CustomVCAddMemberSubcommand(CustomVCRepository dataStorage, 17 | BotConfig botConfig) { 18 | super(new SubcommandData("add-member", "adds a member to the voice channel"), dataStorage, botConfig); 19 | } 20 | 21 | @Override 22 | protected void apply(VoiceChannel vc, Member member, SlashCommandInteractionEvent event) { 23 | vc.upsertPermissionOverride(member) 24 | .setAllowed(Permission.VIEW_CHANNEL) 25 | .queue(); 26 | event 27 | .reply("Successfully added " + member.getAsMention() + " to " + vc.getAsMention() + ". They can now join the voice channel.") 28 | .setEphemeral(true) 29 | .queue(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/configuration/ConfigCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.configuration; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.systems.moderation.CommandModerationPermissions; 6 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 7 | 8 | /** 9 | * The main command for interacting with the bot's configuration at runtime via 10 | * slash commands. 11 | */ 12 | public class ConfigCommand extends SlashCommand implements CommandModerationPermissions { 13 | /** 14 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 15 | * @param botConfig The main configuration of the bot 16 | * @param exportConfigSubcommand /config export 17 | * @param getConfigSubcommand /config get 18 | * @param setConfigSubcommand /config set 19 | */ 20 | public ConfigCommand(BotConfig botConfig, ExportConfigSubcommand exportConfigSubcommand, GetConfigSubcommand getConfigSubcommand, SetConfigSubcommand setConfigSubcommand) { 21 | setModerationSlashCommandData(Commands.slash("config", "Administrative Commands for managing the bot's configuration.")); 22 | addSubcommands(exportConfigSubcommand, getConfigSubcommand, setConfigSubcommand); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/report/ReportUserContext.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.report; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; 7 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 8 | 9 | /** 10 | *

This class represents the "Report User" User Context Menu command.

11 | */ 12 | public class ReportUserContext extends ContextCommand.User { 13 | private final BotConfig botConfig; 14 | 15 | /** 16 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.CommandData}. 17 | * @param botConfig The injected {@link BotConfig} 18 | */ 19 | public ReportUserContext(BotConfig botConfig) { 20 | this.botConfig = botConfig; 21 | setCommandData(Commands.user("Report User") 22 | .setGuildOnly(true) 23 | ); 24 | } 25 | 26 | @Override 27 | public void execute(UserContextInteractionEvent event) { 28 | if (event.getTarget().equals(event.getUser())) { 29 | Responses.error(event, "You cannot perform this action on yourself.").queue(); 30 | return; 31 | } 32 | event.replyModal(new ReportManager(botConfig).buildUserReportModal(event)).queue(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/tags/commands/TagsAdminCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.tags.commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.systems.moderation.CommandModerationPermissions; 5 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 6 | 7 | /** 8 | * Represents the `/tag-admin` command. This holds administrative commands for managing "Custom Tags". 9 | */ 10 | public class TagsAdminCommand extends SlashCommand implements CommandModerationPermissions { 11 | /** 12 | * This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and 13 | * adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.Subcommand}s. 14 | * @param createCustomTagSubcommand /tag-admin create 15 | * @param deleteCustomTagSubcommand /tag-admin delete 16 | * @param editCustomTagSubcommand /tag-admin edit 17 | */ 18 | public TagsAdminCommand(CreateCustomTagSubcommand createCustomTagSubcommand, DeleteCustomTagSubcommand deleteCustomTagSubcommand, EditCustomTagSubcommand editCustomTagSubcommand) { 19 | setModerationSlashCommandData(Commands.slash("tag-admin", "Administrative commands for managing \"Custom Tags\".")); 20 | addSubcommands(createCustomTagSubcommand, deleteCustomTagSubcommand, editCustomTagSubcommand); 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/warn/WarnCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.warn; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.systems.moderation.CommandModerationPermissions; 5 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 6 | 7 | /** 8 | * Represents the `/warn` command. This holds administrative commands for managing user warns. 9 | */ 10 | public class WarnCommand extends SlashCommand implements CommandModerationPermissions { 11 | /** 12 | * This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and 13 | * adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.Subcommand}s. 14 | * @param warnAddSubcommand /warn add 15 | * @param discardWarnByIdSubCommand /warn discard-by-id 16 | * @param discardAllWarnsSubcommand /warn discard-all 17 | * @param exportSubcommand /warn export 18 | */ 19 | public WarnCommand(WarnAddSubcommand warnAddSubcommand, DiscardWarnByIdSubCommand discardWarnByIdSubCommand, DiscardAllWarnsSubcommand discardAllWarnsSubcommand, WarnExportSubcommand exportSubcommand) { 20 | setModerationSlashCommandData(Commands.slash("warn", "Administrative commands for managing user warns.")); 21 | addSubcommands(warnAddSubcommand, discardWarnByIdSubCommand, discardAllWarnsSubcommand, exportSubcommand); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_activity/StaffActivityListener.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_activity; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.data.config.guild.ModerationConfig; 6 | import net.dv8tion.jda.api.entities.Member; 7 | import net.dv8tion.jda.api.events.message.MessageReceivedEvent; 8 | import net.dv8tion.jda.api.hooks.ListenerAdapter; 9 | 10 | /** 11 | * Listener for tracking staff activity. 12 | * Each time the staff member sends a message, the staff activity message is updated. 13 | * @see StaffActivityService 14 | */ 15 | @RequiredArgsConstructor 16 | public class StaffActivityListener extends ListenerAdapter { 17 | 18 | private final BotConfig botConfig; 19 | private final StaffActivityService service; 20 | 21 | @Override 22 | public void onMessageReceived(MessageReceivedEvent event) { 23 | if (!event.isFromGuild()) { 24 | return; 25 | } 26 | if (event.getAuthor().isBot() || event.getAuthor().isSystem()) { 27 | return; 28 | } 29 | ModerationConfig moderationConfig = botConfig.get(event.getGuild()).getModerationConfig(); 30 | Member member = event.getMember(); 31 | if (!member.getRoles().contains(moderationConfig.getStaffRole())) { 32 | return; 33 | } 34 | 35 | service.updateStaffActivity(StaffActivityType.LAST_MESSAGE, event.getMessage().getTimeCreated(), member); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/report/ReportMessageContext.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.report; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; 7 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 8 | 9 | /** 10 | *

This class represents the "Report Message" Message Context Menu command.

11 | */ 12 | public class ReportMessageContext extends ContextCommand.Message { 13 | private final BotConfig botConfig; 14 | 15 | /** 16 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.CommandData}. 17 | * @param botConfig The main configuration of the bot 18 | */ 19 | public ReportMessageContext(BotConfig botConfig) { 20 | this.botConfig = botConfig; 21 | setCommandData(Commands.message("Report Message") 22 | .setGuildOnly(true) 23 | ); 24 | } 25 | 26 | @Override 27 | public void execute(MessageContextInteractionEvent event) { 28 | if (event.getTarget().getAuthor().equals(event.getUser())) { 29 | Responses.error(event, "You cannot perform this action on yourself.").queue(); 30 | return; 31 | } 32 | event.replyModal(new ReportManager(botConfig).buildMessageReportModal(event)).queue(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/StringResourceCache.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.UncheckedIOException; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * Simple utility for reading string contents from resources on the classpath, 12 | * and caching them for the duration of the runtime. 13 | */ 14 | public class StringResourceCache { 15 | private static final Map CACHE = new ConcurrentHashMap<>(); 16 | 17 | private StringResourceCache() { 18 | } 19 | 20 | /** 21 | * Loads a String from the resources folder. 22 | * 23 | * @param resourceName The resources' name & path. 24 | * @return The resources' content as a String. 25 | */ 26 | public static String load(String resourceName) { 27 | String content = CACHE.get(resourceName); 28 | if (content == null) { 29 | try(InputStream is = StringResourceCache.class.getResourceAsStream(resourceName)){ 30 | if (is == null) throw new RuntimeException("Could not load " + resourceName); 31 | content = new String(is.readAllBytes(), StandardCharsets.UTF_8); 32 | }catch (IOException e) { 33 | ExceptionLogger.capture(e, StringResourceCache.class.getSimpleName()); 34 | throw new UncheckedIOException(e); 35 | } 36 | 37 | CACHE.put(resourceName, content); 38 | } 39 | return content; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/self_roles/SelfRoleCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.self_roles; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; 5 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 6 | 7 | /** 8 | * Represents the `/self-role` command. This holds administrative commands for managing the bot's database. 9 | */ 10 | public class SelfRoleCommand extends SlashCommand { 11 | /** 12 | * This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and 13 | * adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.Subcommand}s. 14 | * @param createSelfRoleSubcommand /self-role create 15 | * @param changeSelfRoleStatusSubcommand /self-role status 16 | * @param removeSelfRolesSubcommand /self-role remove-all 17 | */ 18 | public SelfRoleCommand(CreateSelfRoleSubcommand createSelfRoleSubcommand, ChangeSelfRoleStatusSubcommand changeSelfRoleStatusSubcommand, RemoveSelfRolesSubcommand removeSelfRolesSubcommand) { 19 | setCommandData(Commands.slash("self-role", "Administrative Commands for managing Self Roles.") 20 | .setDefaultPermissions(DefaultMemberPermissions.DISABLED) 21 | .setGuildOnly(true) 22 | ); 23 | addSubcommands(createSelfRoleSubcommand, changeSelfRoleStatusSubcommand, removeSelfRolesSubcommand); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/embeds/EmbedCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.embeds; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.systems.moderation.CommandModerationPermissions; 5 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 6 | 7 | /** 8 | * Represents the `/embed` command. This holds administrative commands for creating and editing embed messages. 9 | */ 10 | public class EmbedCommand extends SlashCommand implements CommandModerationPermissions { 11 | /** 12 | * This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and 13 | * adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.Subcommand}s. 14 | * @param createEmbedSubcommand /embed create 15 | * @param editEmbedSubcommand /embed edit 16 | * @param addEmbedFieldSubcommand /embed add-field 17 | * @param removeEmbedFieldSubcommand /embed remove-field 18 | */ 19 | public EmbedCommand(CreateEmbedSubcommand createEmbedSubcommand, EditEmbedSubcommand editEmbedSubcommand, AddEmbedFieldSubcommand addEmbedFieldSubcommand, RemoveEmbedFieldSubcommand removeEmbedFieldSubcommand) { 20 | setModerationSlashCommandData(Commands.slash("embed", "Administrative commands for creating and editing embed messages.")); 21 | addSubcommands(createEmbedSubcommand, editEmbedSubcommand, addEmbedFieldSubcommand, removeEmbedFieldSubcommand); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/model/QOTWQuestion.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.model; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Objects; 8 | 9 | /** 10 | * Represents a single QOTW question. 11 | */ 12 | @Data 13 | @NoArgsConstructor 14 | public class QOTWQuestion implements Comparable { 15 | private long id; 16 | private LocalDateTime createdAt; 17 | private long guildId; 18 | private long createdBy; 19 | private String text; 20 | private boolean used; 21 | private Integer questionNumber; 22 | private int priority; 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (!(o instanceof QOTWQuestion that)) return false; 28 | if (this.getId() == that.getId()) return true; 29 | return this.getText().equals(that.getText()) && this.getGuildId() == that.getGuildId() && this.getCreatedBy() == that.getCreatedBy() && this.getPriority() == that.getPriority() && this.isUsed() == that.isUsed(); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(getId(), getCreatedAt(), getGuildId(), getCreatedBy(), getText(), isUsed(), getPriority()); 35 | } 36 | 37 | @Override 38 | public int compareTo(QOTWQuestion o) { 39 | int result = Integer.compare(this.getPriority(), o.getPriority()); 40 | if (result == 0) { 41 | return this.getCreatedAt().compareTo(o.getCreatedAt()); 42 | } 43 | return result; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_preferences/model/Preference.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_preferences.model; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * Contains all preferences users can set. 7 | */ 8 | public enum Preference { 9 | /** 10 | * Enables/Disables QOTW reminders. 11 | */ 12 | QOTW_REMINDER("Question of the Week Reminder", "false", new BooleanPreference()), 13 | /** 14 | * Enables/Disables DM notifications for dormant help posts. 15 | */ 16 | PRIVATE_DORMANT_NOTIFICATIONS("Send notifications about dormant help post via DM", "true", new BooleanPreference()), 17 | 18 | /** 19 | * Enables/Disables DM notifications for closed help posts. 20 | */ 21 | PRIVATE_CLOSE_NOTIFICATIONS("Send notifications about help posts closed by other users via DM", "true", new BooleanPreference()), 22 | /** 23 | * Enables / Disables AutoCodeFormatter for help posts. 24 | * Used by {@link net.discordjug.javabot.systems.help.AutoCodeFormatter} 25 | */ 26 | FORMAT_UNFORMATTED_CODE("Automatically detect and add missing code syntax highlighting", "true", new BooleanPreference()); 27 | private final String name; 28 | @Getter 29 | private final String defaultState; 30 | @Getter 31 | private final PreferenceType type; 32 | 33 | Preference(String name, String defaultState, PreferenceType type) { 34 | this.name = name; 35 | this.defaultState = defaultState; 36 | this.type = type; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return name; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/configuration/ExportConfigSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.configuration; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.data.config.GuildConfig; 5 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 6 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 7 | import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; 8 | import net.dv8tion.jda.api.utils.FileUpload; 9 | 10 | import javax.annotation.Nonnull; 11 | import java.io.File; 12 | 13 | /** 14 | * Shows a list of all known configuration properties, their type, and their 15 | * current value. 16 | */ 17 | public class ExportConfigSubcommand extends ConfigSubcommand { 18 | 19 | /** 20 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 21 | * @param botConfig The main configuration of the bot 22 | */ 23 | public ExportConfigSubcommand(BotConfig botConfig) { 24 | super(botConfig); 25 | setCommandData(new SubcommandData("export", "Exports a list of all configuration properties, and their current values.")); 26 | } 27 | 28 | @Override 29 | public ReplyCallbackAction handleConfigSubcommand(@Nonnull SlashCommandInteractionEvent event, @Nonnull GuildConfig config) { 30 | return event.deferReply() 31 | .addFiles(FileUpload.fromData(new File("config/" + event.getGuild().getId() + ".json"))); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/leaderboard/qotw/model/QOTWUserData.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes.leaderboard.qotw.model; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import net.discordjug.javabot.api.routes.data.UserData; 6 | import net.discordjug.javabot.systems.qotw.model.QOTWAccount; 7 | import net.dv8tion.jda.api.entities.User; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | /** 13 | * API-Data class which contains all necessary information about a single users' 14 | * QOTW-Account. 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = false) 18 | public class QOTWUserData extends UserData { 19 | private QOTWAccount account; 20 | private int rank; 21 | 22 | /** 23 | * Creates a new {@link QOTWUserData} instance. 24 | * 25 | * @param account The {@link QOTWAccount} to use. 26 | * @param user A nullable {@link User}. 27 | * @param rank The position of the user in the QOTW leaderboard 28 | * @return The {@link QOTWUserData}. 29 | */ 30 | public static @NotNull QOTWUserData of(@NotNull QOTWAccount account, @Nullable User user, int rank) { 31 | QOTWUserData data = new QOTWUserData(); 32 | data.setUserId(account.getUserId()); 33 | if (user != null) { 34 | data.setUserName(user.getName()); 35 | data.setDiscriminator(user.getDiscriminator()); 36 | data.setEffectiveAvatarUrl(user.getEffectiveAvatarUrl()); 37 | } 38 | data.setRank(rank); 39 | data.setAccount(account); 40 | return data; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/role_emoji/RoleEmojiCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.role_emoji; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.dv8tion.jda.api.Permission; 6 | import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; 7 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 8 | import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; 9 | 10 | /** 11 | * This class represents the /emoji-admin command. 12 | * This command allows managing emojis which are usable only be members with certain roles. 13 | */ 14 | public class RoleEmojiCommand extends SlashCommand { 15 | 16 | /** 17 | * The constructor of this class, which sets the corresponding {@link SlashCommandData}s. 18 | * @param botConfig The main configuration of the bot 19 | * @param addRoleEmojiSubcommand A subcommand allowing to add role-exclusive emojis 20 | */ 21 | public RoleEmojiCommand(BotConfig botConfig, AddRoleEmojiSubcommand addRoleEmojiSubcommand) { 22 | SlashCommandData slashCommandData = Commands.slash("emoji-admin", "Administrative command for managing guild emojis") 23 | .setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.ADMINISTRATOR)) 24 | .setGuildOnly(true); 25 | setCommandData(slashCommandData); 26 | addSubcommands(addRoleEmojiSubcommand); 27 | setRequiredUsers(botConfig.getSystems().getAdminConfig().getAdminUsers()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/net/discordjug/javabot/util/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | 8 | /** 9 | * Tests for the {@link StringUtils} class. 10 | */ 11 | public class StringUtilsTest { 12 | 13 | /** 14 | * Tests the {@link StringUtils#buildTextProgressBar(double, int)} method. 15 | */ 16 | @Test 17 | public void testBuildTextProgressBar() { 18 | assertThrows(IllegalArgumentException.class, () -> StringUtils.buildTextProgressBar(0.5, 4)); 19 | assertEquals("[░░░]", StringUtils.buildTextProgressBar(0, 5)); 20 | assertEquals("[░░░]", StringUtils.buildTextProgressBar(-100, 5)); 21 | assertEquals("[███]", StringUtils.buildTextProgressBar(1, 5)); 22 | assertEquals("[███]", StringUtils.buildTextProgressBar(3, 5)); 23 | assertEquals("[░░░░]", StringUtils.buildTextProgressBar(0.1, 6)); 24 | assertEquals("[░░░░]", StringUtils.buildTextProgressBar(0.24, 6)); 25 | assertEquals("[█░░░]", StringUtils.buildTextProgressBar(0.25, 6)); 26 | assertEquals("[█░░░]", StringUtils.buildTextProgressBar(0.45, 6)); 27 | assertEquals("[██░░]", StringUtils.buildTextProgressBar(0.5, 6)); 28 | assertEquals("[██░░]", StringUtils.buildTextProgressBar(0.65, 6)); 29 | assertEquals("[███░]", StringUtils.buildTextProgressBar(0.75, 6)); 30 | assertEquals("[███░]", StringUtils.buildTextProgressBar(0.95, 6)); 31 | assertEquals("[████]", StringUtils.buildTextProgressBar(1, 6)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands.format_code; 2 | 3 | 4 | import net.discordjug.javabot.util.IndentationHelper; 5 | import net.discordjug.javabot.util.StringUtils; 6 | import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; 7 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | *

This class represents the "Format and Indent Code" Message Context command.

16 | */ 17 | public class FormatAndIndentCodeMessageContext extends ContextCommand.Message { 18 | /** 19 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.CommandData}. 20 | */ 21 | public FormatAndIndentCodeMessageContext() { 22 | setCommandData(Commands.message("Format and Indent Code") 23 | .setGuildOnly(true) 24 | ); 25 | } 26 | 27 | @Override 28 | public void execute(@NotNull MessageContextInteractionEvent event) { 29 | event.replyFormat("```java\n%s\n```", IndentationHelper.formatIndentation(StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()), IndentationHelper.IndentationType.TABS)) 30 | .setAllowedMentions(List.of()) 31 | .setComponents(FormatCodeCommand.buildActionRow(event.getTarget(), event.getUser().getIdLong())) 32 | .queue(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/commands/QOTWSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.util.ExceptionLogger; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 7 | import net.dv8tion.jda.api.requests.restaction.interactions.InteractionCallbackAction; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | import org.springframework.dao.DataAccessException; 11 | 12 | /** 13 | * Abstract parent class for all QOTW subcommands, which handles the standard 14 | * behavior of preparing a connection and obtaining the guild id; these two 15 | * things are required for all QOTW subcommands. 16 | */ 17 | public abstract class QOTWSubcommand extends SlashCommand.Subcommand { 18 | @Override 19 | public void execute(@NotNull SlashCommandInteractionEvent event) { 20 | if (event.getGuild() == null) { 21 | Responses.replyGuildOnly(event).queue(); 22 | return; 23 | } 24 | try { 25 | InteractionCallbackAction reply = handleCommand(event, event.getGuild().getIdLong()); 26 | reply.queue(); 27 | } catch (DataAccessException e) { 28 | ExceptionLogger.capture(e, getClass().getSimpleName()); 29 | Responses.error(event, "An error occurred: " + e.getMessage()).queue(); 30 | } 31 | } 32 | 33 | protected abstract InteractionCallbackAction handleCommand(SlashCommandInteractionEvent event, long guildId) throws DataAccessException; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_activity/dao/StaffActivityMessageRepository.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_activity.dao; 2 | 3 | import org.springframework.jdbc.core.JdbcTemplate; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import lombok.RequiredArgsConstructor; 7 | import net.discordjug.javabot.systems.staff_activity.model.StaffActivityMessage; 8 | 9 | /** 10 | * Repository for storing staff activity message locations. 11 | */ 12 | @RequiredArgsConstructor 13 | @Repository 14 | public class StaffActivityMessageRepository { 15 | private final JdbcTemplate jdbcTemplate; 16 | 17 | /** 18 | * Inserts a new {@link StaffActivityMessage} or replaces an old one. 19 | * @param msg the {@link StaffActivityMessage} to store 20 | */ 21 | public void insertOrReplace(StaffActivityMessage msg) { 22 | jdbcTemplate.update(""" 23 | MERGE INTO staff_activity_messages 24 | (guild_id, user_id, message_id) 25 | KEY (guild_id, user_id) 26 | VALUES 27 | (?,?,?) 28 | """, msg.guildId(), msg.userId(), msg.messageId()); 29 | } 30 | 31 | /** 32 | * gets the ID of the activity message of a specific staff member. 33 | * @param guildId the ID of the relevant guild 34 | * @param userId the ID of the staff member 35 | * @return the message ID of the activity message 36 | */ 37 | public Long getMessageId(long guildId, long userId) { 38 | return jdbcTemplate.query("SELECT message_id FROM staff_activity_messages WHERE guild_id=? AND user_id=?", rs-> rs.next() ? (Long)rs.getLong(1) : null, guildId, userId); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/leaderboard/help_experience/model/ExperienceUserData.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes.leaderboard.help_experience.model; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import net.discordjug.javabot.api.routes.data.UserData; 6 | import net.discordjug.javabot.systems.help.model.HelpAccount; 7 | import net.dv8tion.jda.api.entities.User; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | /** 13 | * API-Data class which contains all necessary information about a single users' 14 | * Help-Account. 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = false) 18 | public class ExperienceUserData extends UserData { 19 | private HelpAccount account; 20 | private int rank; 21 | 22 | /** 23 | * Creates a new {@link ExperienceUserData} instance. 24 | * 25 | * @param account The {@link HelpAccount} to use. 26 | * @param user A nullable {@link User}. 27 | * @param rank The position of the user in the help leaderboard. 28 | * @return The {@link ExperienceUserData}. 29 | */ 30 | public static @NotNull ExperienceUserData of(@NotNull HelpAccount account, @Nullable User user, int rank) { 31 | ExperienceUserData data = new ExperienceUserData(); 32 | data.setUserId(account.getUserId()); 33 | if (user != null) { 34 | data.setUserName(user.getName()); 35 | data.setDiscriminator(user.getDiscriminator()); 36 | data.setEffectiveAvatarUrl(user.getEffectiveAvatarUrl()); 37 | } 38 | data.setAccount(account); 39 | data.setRank(rank); 40 | return data; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/help/commands/HelpGuidelinesSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.help.commands; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.util.Responses; 5 | import net.discordjug.javabot.util.StringResourceCache; 6 | import net.dv8tion.jda.api.EmbedBuilder; 7 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 8 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 9 | 10 | import org.jetbrains.annotations.NotNull; 11 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 12 | 13 | /** 14 | * Shows the server's help-guidelines. 15 | */ 16 | public class HelpGuidelinesSubcommand extends SlashCommand.Subcommand { 17 | /** 18 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 19 | * @param botConfig The main configuration of the bot 20 | */ 21 | public HelpGuidelinesSubcommand(BotConfig botConfig) { 22 | setCommandData(new SubcommandData("guidelines", "Show the server's help guidelines in a simple format.")); 23 | } 24 | 25 | @Override 26 | public void execute(@NotNull SlashCommandInteractionEvent event) { 27 | event.replyEmbeds(new EmbedBuilder() 28 | .setTitle("Help Guidelines") 29 | .setColor(Responses.Type.DEFAULT.getColor()) 30 | .setDescription(StringResourceCache.load("/help_guidelines/guidelines_text.txt")) 31 | .setImage(StringResourceCache.load("/help_guidelines/guidelines_image_url.txt")) 32 | .build() 33 | ).queue(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/dao/QOTWChampionRepository.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.dao; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.jdbc.core.JdbcTemplate; 8 | import org.springframework.stereotype.Repository; 9 | 10 | /** 11 | * Repository for the qotw_champion table. 12 | * This table is used for storing the current QOTW champions. 13 | */ 14 | @RequiredArgsConstructor 15 | @Repository 16 | public class QOTWChampionRepository { 17 | private final JdbcTemplate jdbcTemplate; 18 | 19 | /** 20 | * Gets the currently configured QOTW champions. 21 | * @param guildId the ID of the guild to get the champions 22 | * @return a {@link List} containing the Discord user IDs of the current QOTW champions 23 | */ 24 | public List getCurrentQOTWChampions(long guildId) { 25 | return jdbcTemplate.query("SELECT user_id FROM qotw_champion WHERE guild_id = ?", 26 | (rs, row)->{ 27 | return rs.getLong(1); 28 | }, 29 | guildId); 30 | } 31 | 32 | /** 33 | * Sets the current QOTW champions for a specific guild. 34 | * @param guild the guild to set the QOTW champions in 35 | * @param users the QOTW champions 36 | */ 37 | public void setCurrentQOTWChampions(long guild, long[] users) { 38 | jdbcTemplate.update("DELETE FROM qotw_champion WHERE guild_id = ?"); 39 | List params = new ArrayList<>(); 40 | for (long userId : users) { 41 | params.add(new Object[] {guild, userId}); 42 | } 43 | jdbcTemplate.batchUpdate("INSERT INTO qotw_champion (guild_id, user_id) VALUES (?,?)", params); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/help_guidelines/guidelines_text.txt: -------------------------------------------------------------------------------- 1 | **1.** Don't ask questions like _"Can I ask ...?"_ or _"Can someone help me?"_. It's easier for everyone involved if you provide a detailed description of your problem up-front; this makes it more likely for helpers to want to help, and more likely that you'll get an answer quickly. Please provide code snippets and error messages (if any) to help us help you! 2 | 3 | **2.** Please create a post in <#1023632039829831811> for your questions. Do not use other people's posts for your questions. 4 | 5 | **3.** You may use the `/help ping` command if your question is urgent. 6 | *__Abusing this will result in warnings and/or a ban.__* 7 | 8 | **4.** Do not ask for help with exams or homework. You may ask for help with understanding individual concepts and parts of a question, but homework and exam questions that show little effort on your part will most likely go unanswered and may be removed. 9 | 10 | **5.** Do not ask your question if you didn't at least try to solve the problem yourself, are ignorant, or, instead of trying to improve, ask repeating, simple, questions. 11 | 12 | **6.** Format your code using [Discord's triple-backtick syntax](https://support.discord.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-). 13 | 14 | **7.** For reasons similar to those of [Stack Overflow](https://meta.stackoverflow.com/questions/421831/temporary-policy-chatgpt-is-banned), we currently do not allow content created by ChatGPT while helping other people. You may still share its content, when you are not helping somebody and are not looking to deceive others, for example when discussing ChatGPT and its technology. -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/help/checks/SimpleGreetingCheck.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.help.checks; 2 | 3 | import net.dv8tion.jda.api.entities.Message; 4 | import net.dv8tion.jda.api.entities.User; 5 | import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; 6 | import net.dv8tion.jda.api.requests.RestAction; 7 | import net.dv8tion.jda.internal.requests.CompletedRestAction; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Checks if the user just sent only a single greeting and no other information, 15 | * and replies to their message asking them to elaborate on what their question 16 | * is. 17 | */ 18 | @Component 19 | public class SimpleGreetingCheck implements ChannelSemanticCheck { 20 | private static final String[] GREETINGS = {"hi", "hello", "hey", "yo"}; 21 | private static final String MESSAGE = "Hi there! It would be helpful if you could provide a detailed description of your problem."; 22 | 23 | @Override 24 | public RestAction doCheck(TextChannel channel, User owner, List messages, @NotNull ChannelSemanticData semanticData) { 25 | if (semanticData.initialMessage() != null && messages.size() == 1 && !semanticData.containsBotMessageContent(MESSAGE)) { 26 | Message m = semanticData.initialMessage(); 27 | String content = m.getContentStripped().toLowerCase(); 28 | if (content.length() < 25) { 29 | for (String g : GREETINGS) { 30 | if (content.contains(g)) { 31 | return m.reply(MESSAGE); 32 | } 33 | } 34 | } 35 | } 36 | return new CompletedRestAction<>(channel.getJDA(), null); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/net/discordjug/javabot/util/IndentationHelperTest.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | /** 10 | * Test for the {@link IndentationHelper} class. 11 | */ 12 | public class IndentationHelperTest { 13 | /** 14 | * Tests the {@link IndentationHelper#formatIndentation(String, IndentationHelper.IndentationType)} method. 15 | * @throws IOException if any I/O error occurs indicating an issue with the test 16 | */ 17 | @Test 18 | public void testFormatIndentation() throws IOException { 19 | String[] unformatted = null; 20 | String[] formatted = null; 21 | unformatted = StringResourceCache.load("/Unformatted Strings.txt").split("----"); 22 | formatted = StringResourceCache.load("/Formatted Strings.txt").split("----"); 23 | 24 | for (int i = 0, k = 0; i < unformatted.length; i++) { 25 | assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.FOUR_SPACES), "Method failed to format a text with four spaces correctly"); 26 | assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.TWO_SPACES), "Method failed to format a text with two spaces correctly"); 27 | assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.TABS), "Method failed to format a text with tabs correctly."); 28 | assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.NULL), "Method returned a String not matching the input"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/commands/qotw_points/DecrementPointsSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.commands.qotw_points; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import net.discordjug.javabot.systems.notification.NotificationService; 6 | import net.discordjug.javabot.systems.notification.QOTWNotificationService; 7 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; 8 | import net.dv8tion.jda.api.EmbedBuilder; 9 | import net.dv8tion.jda.api.entities.Member; 10 | import net.dv8tion.jda.api.entities.User; 11 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 12 | 13 | /** 14 | *

This class represents the /qotw account increment command.

15 | * This Subcommand allows staff-members to increment the QOTW-points of any user. 16 | */ 17 | public class DecrementPointsSubcommand extends ChangePointsSubcommand { 18 | 19 | public DecrementPointsSubcommand(QOTWPointsService pointsService, NotificationService notificationService) { 20 | super(pointsService, notificationService, "decrement", "Removes one point to the user's QOTW-Account"); 21 | } 22 | 23 | @Override 24 | protected int getIncrementCount(Member targetMember, SlashCommandInteractionEvent event) { 25 | return -1; 26 | } 27 | 28 | @Override 29 | protected @NotNull EmbedBuilder createIncrementEmbedBuilder(User user, long points) { 30 | return super.createIncrementEmbedBuilder(user, points) 31 | .setTitle("QOTW Account decremented"); 32 | } 33 | 34 | @Override 35 | protected void sendUserNotification(@NotNull QOTWNotificationService notificationService, Member member) { 36 | notificationService.sendAccountDecrementedNotification(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/warn/model/WarnSeverity.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.warn.model; 2 | 3 | /** 4 | * Enum class that represents the different Warn Severity's. 5 | */ 6 | public enum WarnSeverity { 7 | /** 8 | * Low severity is intended for small violations. 9 | */ 10 | LOW(20), 11 | 12 | /** 13 | * Medium severity is for more egregious, but still mostly harmless violations. 14 | */ 15 | MEDIUM(40), 16 | 17 | /** 18 | * High severity indicates the user did something intentionally malicious. 19 | */ 20 | HIGH(80); 21 | 22 | /** 23 | * A default weight that is used as a fallback in any case where it is 24 | * impossible to obtain the actual weight for a warning, like if a warning 25 | * document contains an unknown severity value. 26 | */ 27 | public static final int DEFAULT_WEIGHT = 20; 28 | 29 | private final int weight; 30 | 31 | /** 32 | * Constructs the value. 33 | * 34 | * @param weight The weight to use. 35 | */ 36 | WarnSeverity(int weight) { 37 | this.weight = weight; 38 | } 39 | 40 | /** 41 | * Gets the weight for a given severity name. 42 | * 43 | * @param name The name of the severity level. 44 | * @return The weight for the given severity, or {@link #DEFAULT_WEIGHT} if 45 | * no matching severity could be found. 46 | */ 47 | public static int getWeightOrDefault(String name) { 48 | for (WarnSeverity v : values()) { 49 | if (v.name().equalsIgnoreCase(name)) { 50 | return v.weight; 51 | } 52 | } 53 | return DEFAULT_WEIGHT; 54 | } 55 | 56 | /** 57 | * Gets the weight based on the severity. 58 | * 59 | * @return The severity's weight. 60 | */ 61 | public int getWeight() { 62 | return this.weight; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/ImageCache.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.awt.image.BufferedImage; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Utility class for caching images. 11 | */ 12 | @Slf4j 13 | public class ImageCache { 14 | private static final Map cache; 15 | 16 | static { 17 | cache = new HashMap<>(); 18 | } 19 | 20 | private ImageCache() { 21 | } 22 | 23 | /** 24 | * Caches an image by saving it in a {@link Map}. 25 | * 26 | * @param image The image to cache. 27 | * @param name The name of the image. 28 | */ 29 | public static void cacheImage(String name, BufferedImage image) { 30 | log.info("Added Image to Cache: {}", name); 31 | cache.put(name, image); 32 | } 33 | 34 | /** 35 | * Gets an image from the {@link Map}. 36 | * 37 | * @param name The name of the image. 38 | * @return A {@link BufferedImage}. 39 | */ 40 | public static BufferedImage getCachedImage(String name) { 41 | log.info("Retrieved Image from Cache: {}", name); 42 | return cache.get(name); 43 | } 44 | 45 | /** 46 | * Removes an image from the {@link Map} whose name contains the specified keyword. 47 | * 48 | * @param keyword The keyword. 49 | * @return A {@link BufferedImage}. 50 | */ 51 | public static boolean removeCachedImagesByKeyword(String keyword) { 52 | return cache.keySet().removeIf(s -> s.contains(keyword)); 53 | } 54 | 55 | /** 56 | * Checks if an Image is cached. 57 | * 58 | * @param name The name of the image. 59 | * @return Whether the image is already cached or not. 60 | */ 61 | public static boolean isCached(String name) { 62 | return cache.containsKey(name); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/net/discordjug/javabot/systems/qotw/QOTWPointsServiceTest.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw; 2 | 3 | 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import java.util.List; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import net.discordjug.javabot.systems.qotw.model.QOTWAccount; 11 | 12 | class QOTWPointsServiceTest { 13 | 14 | private QOTWPointsService pointsService = new QOTWPointsService(null); 15 | 16 | @Test 17 | void testGetQOTWRankNotPresent() { 18 | assertEquals(-1, pointsService.getQOTWRank(0, List.of())); 19 | assertEquals(-1, pointsService.getQOTWRank(0, List.of(createAccount(1, 1)))); 20 | } 21 | 22 | @Test 23 | void testNormalQOTWRank() { 24 | assertEquals(1, pointsService.getQOTWRank(1, List.of(createAccount(1, 1)))); 25 | assertEquals(2, pointsService.getQOTWRank(2, List.of( 26 | createAccount(1, 2), 27 | createAccount(2, 1) 28 | ))); 29 | } 30 | 31 | @Test 32 | void testQOTWRankWithTiesBefore() { 33 | assertEquals(3, pointsService.getQOTWRank(1, List.of( 34 | createAccount(2, 2), 35 | createAccount(3, 2), 36 | createAccount(1, 1) 37 | ))); 38 | } 39 | 40 | @Test 41 | void testQOTWRankWithTiesAtSamePosition() { 42 | assertEquals(2, pointsService.getQOTWRank(1, List.of( 43 | createAccount(2, 2), 44 | createAccount(3, 1), 45 | createAccount(1, 1) 46 | ))); 47 | assertEquals(2, pointsService.getQOTWRank(1, List.of( 48 | createAccount(2, 2), 49 | createAccount(1, 1), 50 | createAccount(3, 1) 51 | ))); 52 | } 53 | 54 | private QOTWAccount createAccount(long userId, int score) { 55 | QOTWAccount account = new QOTWAccount(); 56 | account.setUserId(userId); 57 | account.setPoints(score); 58 | return account; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/suggestions/SuggestionCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.suggestions; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.systems.moderation.CommandModerationPermissions; 5 | import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; 6 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 7 | 8 | /** 9 | * Represents the `/suggestion` command. This holds administrative commands for managing server suggestions. 10 | */ 11 | public class SuggestionCommand extends SlashCommand implements CommandModerationPermissions { 12 | /** 13 | * This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and 14 | * adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.Subcommand}s. 15 | * @param acceptSuggestionSubcommand /suggestion accept 16 | * @param declineSuggestionSubcommand /suggestion decline 17 | * @param clearSuggestionSubcommand /suggestion clean 18 | * @param onHoldSuggestionSubcommand /suggestion on-hold 19 | */ 20 | public SuggestionCommand(AcceptSuggestionSubcommand acceptSuggestionSubcommand, DeclineSuggestionSubcommand declineSuggestionSubcommand, ClearSuggestionSubcommand clearSuggestionSubcommand, OnHoldSuggestionSubcommand onHoldSuggestionSubcommand) { 21 | setModerationSlashCommandData(Commands.slash("suggestion", "Administrative commands for managing suggestions.") 22 | .setDefaultPermissions(DefaultMemberPermissions.DISABLED) 23 | .setGuildOnly(true) 24 | ); 25 | addSubcommands(acceptSuggestionSubcommand, declineSuggestionSubcommand, clearSuggestionSubcommand, onHoldSuggestionSubcommand); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/listener/filter/BlacklistedMessageAttachmentFilter.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.listener.filter; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.data.config.GuildConfig; 6 | import net.dv8tion.jda.api.EmbedBuilder; 7 | import net.dv8tion.jda.api.entities.Message; 8 | import net.dv8tion.jda.api.entities.MessageEmbed; 9 | import net.dv8tion.jda.api.events.message.MessageReceivedEvent; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * This {@link MessageFilter} blocks attachments blacklisted using {@link GuildConfig}. 16 | */ 17 | @Component 18 | @RequiredArgsConstructor 19 | public class BlacklistedMessageAttachmentFilter implements MessageFilter { 20 | 21 | private final BotConfig botConfig; 22 | 23 | @Override 24 | public MessageModificationStatus processMessage(MessageContent content) { 25 | MessageReceivedEvent event = content.event(); 26 | List attachments = content.attachments(); 27 | List embeds = content.embeds(); 28 | GuildConfig guildConfig = botConfig.get(event.getGuild()); 29 | List blacklistedMessageExtensions = guildConfig.getBlacklistedMessageExtensions(); 30 | boolean removed = attachments.removeIf(attachment -> blacklistedMessageExtensions.contains(attachment.getFileExtension())); 31 | if (removed) { 32 | MessageEmbed attachmentRemovedInfo = new EmbedBuilder() 33 | .setDescription("Disallowed attachments have been removed from this message.") 34 | .build(); 35 | embeds.add(attachmentRemovedInfo); 36 | return MessageModificationStatus.MODIFIED; 37 | } else { 38 | return MessageModificationStatus.NOT_MODIFIED; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/report/ReportCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.report; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.systems.moderation.ModerateUserCommand; 5 | import net.dv8tion.jda.api.entities.Member; 6 | import net.dv8tion.jda.api.entities.Message; 7 | import net.dv8tion.jda.api.entities.User; 8 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 9 | import net.dv8tion.jda.api.interactions.commands.OptionType; 10 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 11 | import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; 12 | 13 | import javax.annotation.Nonnull; 14 | 15 | /** 16 | *

This class represents the /report command.

17 | */ 18 | public class ReportCommand extends ModerateUserCommand { 19 | /** 20 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 21 | * @param botConfig The main configuration of the bot 22 | */ 23 | public ReportCommand(BotConfig botConfig) { 24 | super(botConfig); 25 | setCommandData(Commands.slash("report", "Reports a member.") 26 | .addOption(OptionType.USER, "user", "The user you want to report", true) 27 | .addOption(OptionType.STRING, "reason", "The reason", true) 28 | .setGuildOnly(true) 29 | ); 30 | setRequireStaff(false); 31 | } 32 | 33 | @Override 34 | protected WebhookMessageCreateAction handleModerationUserCommand(@Nonnull SlashCommandInteractionEvent event, @Nonnull Member commandUser, @Nonnull User target, @Nonnull String reason) { 35 | return new ReportManager(botConfig).handleUserReport(event.getHook(), reason, target.getId()); 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/help/HelpExperienceJob.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.help; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.data.config.guild.HelpConfig; 6 | import net.discordjug.javabot.data.h2db.DbHelper; 7 | import net.discordjug.javabot.systems.help.dao.HelpAccountRepository; 8 | import net.discordjug.javabot.util.ExceptionLogger; 9 | import net.dv8tion.jda.api.JDA; 10 | 11 | import org.springframework.dao.DataAccessException; 12 | import org.springframework.scheduling.annotation.Scheduled; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.concurrent.ExecutorService; 16 | 17 | /** 18 | * Removes a specified amount of experience from everyone's help account. 19 | */ 20 | @Service 21 | @RequiredArgsConstructor 22 | public class HelpExperienceJob { 23 | private final JDA jda; 24 | private final BotConfig botConfig; 25 | private final ExecutorService asyncPool; 26 | private final HelpAccountRepository helpAccountRepository; 27 | 28 | /** 29 | * Removes a specified amount of experience from everyone's help account. 30 | */ 31 | @Scheduled(cron = "0 0 0 * * *") // Daily, 00:00 UTC 32 | public void execute() { 33 | asyncPool.execute(() -> { 34 | try { 35 | // just get the config for the first guild the bot is in, as it's not designed to work in multiple guilds anyway 36 | HelpConfig helpConfig = botConfig.get(jda.getGuilds().get(0)).getHelpConfig(); 37 | helpAccountRepository.removeExperienceFromAllAccounts( 38 | helpConfig.getDailyExperienceSubtraction(), 39 | helpConfig.getMinDailyExperienceSubtraction(), 40 | helpConfig.getMaxDailyExperienceSubtraction()); 41 | } catch (DataAccessException e) { 42 | ExceptionLogger.capture(e, DbHelper.class.getSimpleName()); 43 | } 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWReviewReminderJob.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.jobs; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.data.config.GuildConfig; 6 | import net.discordjug.javabot.systems.notification.NotificationService; 7 | import net.dv8tion.jda.api.JDA; 8 | import net.dv8tion.jda.api.entities.Guild; 9 | import net.dv8tion.jda.api.entities.Message; 10 | 11 | import java.util.EnumSet; 12 | 13 | import org.springframework.scheduling.annotation.Scheduled; 14 | import org.springframework.stereotype.Service; 15 | 16 | /** 17 | * Job which reminds reviewers to review QOTW if this has not been done already. 18 | */ 19 | @Service 20 | @RequiredArgsConstructor 21 | public class QOTWReviewReminderJob { 22 | private final JDA jda; 23 | private final NotificationService notificationService; 24 | private final BotConfig botConfig; 25 | 26 | /** 27 | * Reminds reviewers to review QOTW if this has not been done already. 28 | */ 29 | @Scheduled(cron = "0 0 7 * * 1") // Monday, 07:00 UTC 30 | public void execute() { 31 | for (Guild guild : jda.getGuilds()) { 32 | GuildConfig guildConfig = botConfig.get(guild); 33 | if (guildConfig.getModerationConfig().getLogChannel() == null || guildConfig.getQotwConfig().getSubmissionChannel()==null) { 34 | continue; 35 | } 36 | if (!guildConfig.getQotwConfig().getSubmissionChannel().getThreadChannels().isEmpty()) { 37 | notificationService 38 | .withGuild(guild) 39 | .sendToModerationLog(channel -> 40 | channel 41 | .sendMessageFormat("%s\n**Reminder**\nThe QOTW has not been reviewed yet.", 42 | guildConfig.getQotwConfig().getQOTWReviewRole().getAsMention()) 43 | .setAllowedMentions(EnumSet.of(Message.MentionType.ROLE)) 44 | ); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/server_lock/CheckLockStatusSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.server_lock; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.data.config.guild.ServerLockConfig; 6 | import net.discordjug.javabot.util.Checks; 7 | import net.discordjug.javabot.util.Responses; 8 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 9 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 10 | 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | /** 14 | *

This class represents the /serverlock-admin check-status command.

15 | */ 16 | public class CheckLockStatusSubcommand extends SlashCommand.Subcommand { 17 | private final BotConfig botConfig; 18 | 19 | /** 20 | * The constructor of this class, which sets the corresponding {@link SubcommandData}. 21 | * @param botConfig The main configuration of the bot 22 | */ 23 | public CheckLockStatusSubcommand(BotConfig botConfig) { 24 | this.botConfig = botConfig; 25 | setCommandData(new SubcommandData("check-status", "Command for checking the current server lock status.")); 26 | } 27 | 28 | @Override 29 | public void execute(@NotNull SlashCommandInteractionEvent event) { 30 | if (event.getGuild() == null || event.getMember() == null) { 31 | Responses.replyGuildOnly(event).queue(); 32 | return; 33 | } 34 | if (!Checks.hasStaffRole(botConfig, event.getMember())) { 35 | Responses.replyStaffOnly(event, botConfig.get(event.getGuild())).queue(); 36 | return; 37 | } 38 | ServerLockConfig config = botConfig.get(event.getGuild()).getServerLockConfig(); 39 | Responses.info(event, "Server Lock Status", "The Server Lock is currently **%s**", config.isLocked() ? "ENABLED" : "DISABLED").queue(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWReminderJob.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.jobs; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.data.config.guild.QOTWConfig; 5 | import net.discordjug.javabot.systems.notification.NotificationService; 6 | import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository; 7 | import net.discordjug.javabot.systems.qotw.model.QOTWQuestion; 8 | import net.dv8tion.jda.api.JDA; 9 | import net.dv8tion.jda.api.entities.Guild; 10 | 11 | import org.springframework.scheduling.annotation.Scheduled; 12 | import org.springframework.stereotype.Service; 13 | 14 | import lombok.RequiredArgsConstructor; 15 | 16 | import java.sql.SQLException; 17 | import java.util.Optional; 18 | 19 | /** 20 | * Checks that there's a question in the QOTW queue ready for posting soon. 21 | */ 22 | @Service 23 | @RequiredArgsConstructor 24 | public class QOTWReminderJob { 25 | private final JDA jda; 26 | private final NotificationService notificationService; 27 | private final BotConfig botConfig; 28 | private final QuestionQueueRepository questionQueueRepository; 29 | 30 | /** 31 | * Checks that there's a question in the QOTW queue ready for posting soon. 32 | * @throws SQLException if an SQL error occurs 33 | */ 34 | @Scheduled(cron = "0 0 9 * * *") // Daily, 09:00 UTC 35 | public void execute() throws SQLException { 36 | for (Guild guild : jda.getGuilds()) { 37 | QOTWConfig config = botConfig.get(guild).getQotwConfig(); 38 | Optional q = questionQueueRepository.getNextQuestion(guild.getIdLong()); 39 | if (q.isEmpty()) { 40 | notificationService.withGuild(guild).sendToModerationLog(m -> m.sendMessageFormat( 41 | "Warning! %s There's no Question of the Week in the queue. Please add one before it's time to post!", 42 | config.getQOTWReviewRole().getAsMention())); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/commands/qotw_points/IncrementPointsSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.commands.qotw_points; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.systems.notification.NotificationService; 6 | import net.discordjug.javabot.systems.notification.QOTWNotificationService; 7 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; 8 | import net.dv8tion.jda.api.EmbedBuilder; 9 | import net.dv8tion.jda.api.entities.Member; 10 | import net.dv8tion.jda.api.entities.User; 11 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 12 | 13 | /** 14 | *

This class represents the /qotw account increment command.

15 | * This Subcommand allows staff-members to increment the QOTW-points of any user. 16 | */ 17 | public class IncrementPointsSubcommand extends ChangePointsSubcommand { 18 | 19 | private final BotConfig botConfig; 20 | 21 | public IncrementPointsSubcommand(QOTWPointsService pointsService, NotificationService notificationService, BotConfig botConfig) { 22 | super(pointsService, notificationService, "increment", "Adds one point to the user's QOTW-Account"); 23 | this.botConfig = botConfig; 24 | } 25 | 26 | @Override 27 | protected int getIncrementCount(Member targetMember, SlashCommandInteractionEvent event) { 28 | return 1; 29 | } 30 | 31 | @Override 32 | protected @NotNull EmbedBuilder createIncrementEmbedBuilder(User user, long points) { 33 | return super.createIncrementEmbedBuilder(user, points) 34 | .setTitle("QOTW Account incremented"); 35 | } 36 | 37 | @Override 38 | protected void sendUserNotification(@NotNull QOTWNotificationService notificationService, Member member) { 39 | notificationService.sendAccountIncrementedNotification(botConfig.get(member.getGuild()).getQotwConfig().getSubmissionsForumChannel()); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/listener/QOTWSubmissionListener.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.listener; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.data.config.guild.QOTWConfig; 6 | import net.discordjug.javabot.util.InteractionUtils; 7 | import net.dv8tion.jda.api.entities.channel.ChannelType; 8 | import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; 9 | import net.dv8tion.jda.api.events.message.MessageReceivedEvent; 10 | import net.dv8tion.jda.api.hooks.ListenerAdapter; 11 | 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | /** 15 | * Listens for new messages inside QOTW submissions and warns the author about the webhook limitations. 16 | */ 17 | @RequiredArgsConstructor 18 | public class QOTWSubmissionListener extends ListenerAdapter { 19 | private final BotConfig botConfig; 20 | 21 | @Override 22 | public void onMessageReceived(@NotNull MessageReceivedEvent event) { 23 | if (!event.isFromGuild() || !event.isFromThread() || event.getChannelType() != ChannelType.GUILD_PRIVATE_THREAD) { 24 | return; 25 | } 26 | QOTWConfig qotwConfig = botConfig.get(event.getGuild()).getQotwConfig(); 27 | ThreadChannel thread = event.getChannel().asThreadChannel(); 28 | // TODO: fix check 29 | if (thread.getParentChannel().getIdLong() != qotwConfig.getSubmissionChannelId()) { 30 | return; 31 | } 32 | if (event.getMessage().getContentRaw().length() > 2000) { 33 | event.getChannel().sendMessageFormat(""" 34 | Hey %s! 35 | Please keep in mind that messages **over 2000 characters** get split in half due to webhook limitations. 36 | If you want to make sure that your submission is properly formatted, split your message into smaller chunks instead.""", 37 | event.getAuthor().getAsMention()) 38 | .setActionRow(InteractionUtils.createDeleteButton(event.getAuthor().getIdLong())) 39 | .queue(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/notification/GuildNotificationService.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.notification; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import net.discordjug.javabot.data.config.GuildConfig; 7 | import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; 8 | import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.function.Function; 12 | 13 | /** 14 | * Handles all sorts of guild notifications. 15 | */ 16 | @Slf4j 17 | @RequiredArgsConstructor(access = AccessLevel.PACKAGE) 18 | public final class GuildNotificationService extends NotificationService.MessageChannelNotification { 19 | 20 | private final GuildConfig guildConfig; 21 | 22 | /** 23 | * Sends a notification to the log channel. 24 | * 25 | * @param function The {@link Function} to use which MUST return a {@link MessageCreateAction}. 26 | */ 27 | public void sendToModerationLog(@NotNull Function function) { 28 | MessageChannel channel = guildConfig.getModerationConfig().getLogChannel(); 29 | if (channel == null) { 30 | log.error("Could not send message to LogChannel in guild " + guildConfig.getGuild().getId()); 31 | return; 32 | } 33 | send(channel, function); 34 | } 35 | 36 | /** 37 | * Sends a notification to the message cache log channel. 38 | * 39 | * @param function The {@link Function} to use which MUST return a {@link MessageCreateAction}. 40 | */ 41 | public void sendToMessageLog(@NotNull Function function) { 42 | MessageChannel channel = guildConfig.getMessageCacheConfig().getMessageCacheLogChannel(); 43 | if (channel == null) { 44 | log.error("Could not find MessageCacheLogChannel in guild " + guildConfig.getGuild().getId()); 45 | return; 46 | } 47 | send(channel, function); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/help/commands/CloseCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.help.commands; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.data.h2db.DbActions; 5 | import net.discordjug.javabot.systems.help.dao.HelpAccountRepository; 6 | import net.discordjug.javabot.systems.help.dao.HelpTransactionRepository; 7 | import net.discordjug.javabot.systems.user_preferences.UserPreferenceService; 8 | import net.dv8tion.jda.api.interactions.commands.OptionType; 9 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 10 | 11 | /** 12 | * A simple command that can be used inside reserved help channels to immediately unreserve them, 13 | * instead of waiting for a timeout. 14 | * An alias to /unreserve. 15 | */ 16 | public class CloseCommand extends UnreserveCommand { 17 | 18 | /** 19 | * The constructor of this class, which sets the corresponding 20 | * {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 21 | * @param botConfig The main configuration of the bot 22 | * @param dbActions A utility object providing various operations on the main database 23 | * @param helpTransactionRepository Dao object that represents the HELP_TRANSACTION SQL Table. 24 | * @param helpAccountRepository Dao object that represents the HELP_ACCOUNT SQL Table. 25 | * @param preferenceService Service for user preferences 26 | */ 27 | public CloseCommand(BotConfig botConfig, DbActions dbActions, HelpTransactionRepository helpTransactionRepository, HelpAccountRepository helpAccountRepository, UserPreferenceService preferenceService) { 28 | super(botConfig, dbActions, helpTransactionRepository, helpAccountRepository, preferenceService); 29 | setCommandData( 30 | Commands.slash("close", "Unreserves this post marking your question/issue as resolved.") 31 | .setGuildOnly(true).addOption(OptionType.STRING, "reason", 32 | "The reason why you're unreserving this channel", false)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/tags/commands/TagsSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.tags.commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | import net.discordjug.javabot.data.config.BotConfig; 7 | import net.discordjug.javabot.util.Checks; 8 | import net.discordjug.javabot.util.ExceptionLogger; 9 | import net.discordjug.javabot.util.Responses; 10 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 11 | import net.dv8tion.jda.api.requests.RestAction; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.sql.SQLException; 16 | 17 | /** 18 | * An abstraction of {@link xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand.Subcommand} which handles all 19 | * custom-tag-related commands. 20 | */ 21 | @RequiredArgsConstructor 22 | public abstract class TagsSubcommand extends SlashCommand.Subcommand { 23 | private boolean requireStaff = true; 24 | private final BotConfig botConfig; 25 | 26 | @Override 27 | public void execute(@NotNull SlashCommandInteractionEvent event) { 28 | if (event.getGuild() == null || event.getMember() == null) { 29 | Responses.replyGuildOnly(event).queue(); 30 | return; 31 | } 32 | if (requireStaff && !Checks.hasStaffRole(botConfig, event.getMember())) { 33 | Responses.replyStaffOnly(event, botConfig.get(event.getGuild())).queue(); 34 | return; 35 | } 36 | try { 37 | handleCustomTagsSubcommand(event).queue(); 38 | } catch (SQLException e) { 39 | ExceptionLogger.capture(e, getClass().getSimpleName()); 40 | Responses.error(event, "An error occurred while executing this command.").queue(); 41 | } 42 | } 43 | 44 | protected void setRequiredStaff(boolean requireStaff) { 45 | this.requireStaff = requireStaff; 46 | } 47 | 48 | protected abstract RestAction handleCustomTagsSubcommand(@NotNull SlashCommandInteractionEvent event) throws SQLException; 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/timeout/TimeoutSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.timeout; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.util.Checks; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.dv8tion.jda.api.Permission; 7 | import net.dv8tion.jda.api.entities.Member; 8 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 9 | import net.dv8tion.jda.api.interactions.commands.OptionMapping; 10 | import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; 11 | 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | /** 15 | * An abstraction of {@link xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand.Subcommand} which handles all 16 | * timeout-related commands. 17 | */ 18 | public abstract class TimeoutSubcommand extends SlashCommand.Subcommand { 19 | @Override 20 | public void execute(@NotNull SlashCommandInteractionEvent event) { 21 | if (event.getGuild() == null) { 22 | Responses.replyGuildOnly(event).queue(); 23 | return; 24 | } 25 | if (!Checks.hasPermission(event.getGuild(), Permission.MODERATE_MEMBERS)) { 26 | Responses.replyInsufficientPermissions(event, Permission.MODERATE_MEMBERS).queue(); 27 | return; 28 | } 29 | OptionMapping memberMapping = event.getOption("member"); 30 | if (memberMapping == null) { 31 | Responses.replyMissingArguments(event).queue(); 32 | return; 33 | } 34 | Member member = memberMapping.getAsMember(); 35 | if (member == null) { 36 | Responses.replyMissingMember(event).queue(); 37 | return; 38 | } 39 | if (!event.getGuild().getSelfMember().canInteract(member)) { 40 | Responses.replyCannotInteract(event, member).queue(); 41 | return; 42 | } 43 | handleTimeoutCommand(event, member).queue(); 44 | } 45 | 46 | protected abstract ReplyCallbackAction handleTimeoutCommand(@NotNull SlashCommandInteractionEvent event, @NotNull Member member); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/listener/VotingRegulationListener.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.listener; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.dv8tion.jda.api.entities.Message; 6 | import net.dv8tion.jda.api.entities.emoji.Emoji; 7 | import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; 8 | import net.dv8tion.jda.api.hooks.ListenerAdapter; 9 | 10 | /** 11 | * Makes sure users don't vote on/star their own messages. 12 | */ 13 | @RequiredArgsConstructor 14 | public class VotingRegulationListener extends ListenerAdapter{ 15 | 16 | private final BotConfig botConfig; 17 | 18 | @Override 19 | public void onMessageReactionAdd(MessageReactionAddEvent event) { 20 | if (event.getUser().isBot() || event.getUser().isSystem()) { 21 | return; 22 | } 23 | if(isCriticalEmoji(event)) { 24 | event.retrieveMessage().queue(msg->{ 25 | if(doesAuthorMatch(event.getUserIdLong(), msg)) { 26 | msg.removeReaction(event.getEmoji(), event.getUser()).queue(); 27 | } 28 | }); 29 | } 30 | } 31 | 32 | private boolean doesAuthorMatch(long userId, Message msg) { 33 | long suggestionChannelId = botConfig.get(msg.getGuild()).getModerationConfig().getSuggestionChannelId(); 34 | return msg.getAuthor().getIdLong() == userId|| 35 | msg.getChannel().getIdLong() == suggestionChannelId && 36 | !msg.getEmbeds().isEmpty() && 37 | msg.getEmbeds().get(0).getFooter() != null && 38 | String.valueOf(userId).equals(msg.getEmbeds().get(0).getFooter().getText()); 39 | } 40 | 41 | private boolean isCriticalEmoji(MessageReactionAddEvent event) { 42 | return event.getEmoji().equals(getUpvoteEmoji(event)) || 43 | event.getEmoji().getType() == Emoji.Type.UNICODE && 44 | botConfig.get(event.getGuild()).getStarboardConfig().getEmojis().contains(event.getEmoji().asUnicode()); 45 | } 46 | 47 | private Emoji getUpvoteEmoji(MessageReactionAddEvent event) { 48 | return botConfig.getSystems().getEmojiConfig().getUpvoteEmote(event.getJDA()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/net/discordjug/javabot/util/TimeUtilsTest.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.time.*; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | /** 12 | * Tests the functionality of the {@link TimeUtils} class. 13 | */ 14 | public class TimeUtilsTest { 15 | 16 | /** 17 | * Tests durations. 18 | */ 19 | @Test 20 | public void testDurationToNow() { 21 | OffsetDateTime now = OffsetDateTime.of(2020, 6, 1, 10, 16, 45, 0, ZoneOffset.UTC); 22 | TimeUtils tu = new TimeUtils(Clock.fixed(now.toInstant(), ZoneOffset.UTC)); 23 | assertEquals(3, tu.durationToNow(now.minusDays(3)).toDays()); 24 | assertEquals(2, tu.durationToNow(now.minusHours(2).atZoneSameInstant(ZoneId.of("EST", ZoneId.SHORT_IDS)).toOffsetDateTime()).toHours()); 25 | } 26 | 27 | /** 28 | * Tests the duration formatting. 29 | */ 30 | @Test 31 | public void testFormatDuration() { 32 | Map cases = new HashMap<>(); 33 | cases.put(Duration.ofDays(0), "0 milliseconds"); 34 | cases.put(Duration.ofDays(1), "1 day"); 35 | cases.put(Duration.ofDays(2), "2 days"); 36 | cases.put(Duration.ofMillis(2), "2 milliseconds"); 37 | cases.put(Duration.ofMillis(1), "1 millisecond"); 38 | cases.put(Duration.ofSeconds(1), "1 second"); 39 | cases.put(Duration.ofSeconds(3), "3 seconds"); 40 | cases.put(Duration.ofMinutes(1), "1 minute"); 41 | cases.put(Duration.ofMinutes(45), "45 minutes"); 42 | cases.put(Duration.ofHours(1), "1 hour"); 43 | cases.put(Duration.ofHours(2), "2 hours"); 44 | cases.put(Duration.ofDays(366), "1 year, 1 day"); 45 | cases.put(Duration.ofDays(365), "1 year"); 46 | cases.put(Duration.ofDays(730), "2 years"); 47 | cases.put(Duration.ofDays(731), "2 years, 1 day"); 48 | cases.put(Duration.ofDays(732), "2 years, 2 days"); 49 | 50 | for (Map.Entry c : cases.entrySet()) { 51 | String actual = TimeUtils.formatDuration(c.getKey()); 52 | assertEquals(c.getValue(), actual); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/util/ImageGenerationUtils.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import javax.imageio.ImageIO; 6 | import java.awt.*; 7 | import java.awt.image.BufferedImage; 8 | import java.io.IOException; 9 | import java.net.URL; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | 13 | /** 14 | * Utility class for generating images. 15 | */ 16 | @Slf4j 17 | public class ImageGenerationUtils { 18 | private ImageGenerationUtils() {} 19 | 20 | /** 21 | * Gets an Image from the specified URL. 22 | * 23 | * @param url The url of the image. 24 | * @return The image as a {@link BufferedImage} 25 | * @throws IOException If an error occurs. 26 | */ 27 | public static BufferedImage getImageFromUrl(String url) throws IOException { 28 | return ImageIO.read(new URL(url)); 29 | } 30 | 31 | /** 32 | * Gets an Image from the specified Resource Path. 33 | * 34 | * @param path The path of the image. 35 | * @return The image as a {@link BufferedImage} 36 | * @throws IOException If an error occurs. 37 | */ 38 | public static BufferedImage getResourceImage(String path) throws IOException { 39 | return ImageIO.read(Objects.requireNonNull(ImageGenerationUtils.class.getClassLoader().getResourceAsStream(path))); 40 | } 41 | 42 | /** 43 | * Gets a Font from the specified Resource Path. 44 | * 45 | * @param path The path of the font. 46 | * @param size The font's size. 47 | * @return The font as an {@link Optional} 48 | */ 49 | public static Optional getResourceFont(String path, float size) { 50 | Font font = null; 51 | try { 52 | font = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(ImageGenerationUtils.class.getClassLoader().getResourceAsStream(path))).deriveFont(size); 53 | GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font); 54 | } catch (IOException | FontFormatException e) { 55 | ExceptionLogger.capture(e, ImageGenerationUtils.class.getSimpleName()); 56 | log.warn("Could not load Font from path " + path); 57 | } 58 | return Optional.ofNullable(font); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/warn/WarnsListContext.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.warn; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; 4 | import net.discordjug.javabot.systems.moderation.ModerationService; 5 | import net.discordjug.javabot.util.ExceptionLogger; 6 | import net.discordjug.javabot.util.Responses; 7 | import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; 8 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 9 | 10 | import java.util.concurrent.ExecutorService; 11 | 12 | import org.springframework.dao.DataAccessException; 13 | 14 | /** 15 | *

This class represents the "Show Warns" User Context Menu command.

16 | * This Command allows users to see all their active warns. 17 | */ 18 | public class WarnsListContext extends ContextCommand.User { 19 | private final ExecutorService asyncPool; 20 | private final ModerationService moderationService; 21 | 22 | /** 23 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.CommandData}. 24 | * @param asyncPool The main thread pool for asynchronous operations 25 | * @param moderationService Service object for moderating members 26 | */ 27 | public WarnsListContext(ExecutorService asyncPool, ModerationService moderationService) { 28 | this.asyncPool = asyncPool; 29 | this.moderationService = moderationService; 30 | setCommandData(Commands.user("Show Warns") 31 | .setGuildOnly(true) 32 | ); 33 | } 34 | 35 | @Override 36 | public void execute(UserContextInteractionEvent event) { 37 | if (event.getGuild() == null) { 38 | Responses.replyGuildOnly(event).queue(); 39 | return; 40 | } 41 | event.deferReply(false).queue(); 42 | asyncPool.execute(() -> { 43 | try { 44 | event.getHook().sendMessageEmbeds(WarnsListCommand.buildWarnsEmbed(moderationService.getTotalSeverityWeight(event.getGuild(), event.getTarget().getIdLong()), event.getTarget())).queue(); 45 | } catch (DataAccessException e) { 46 | ExceptionLogger.capture(e, WarnsListContext.class.getSimpleName()); 47 | } 48 | }); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/staff_commands/embeds/EmbedSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.staff_commands.embeds; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | import net.discordjug.javabot.data.config.BotConfig; 7 | import net.discordjug.javabot.util.Checks; 8 | import net.discordjug.javabot.util.Responses; 9 | import net.dv8tion.jda.api.Permission; 10 | import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; 11 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 12 | import net.dv8tion.jda.api.interactions.commands.OptionMapping; 13 | 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | /** 17 | * Abstract parent class for all edit-embed subcommands. 18 | */ 19 | @RequiredArgsConstructor 20 | public abstract class EmbedSubcommand extends SlashCommand.Subcommand { 21 | private final BotConfig botConfig; 22 | 23 | @Override 24 | public void execute(@NotNull SlashCommandInteractionEvent event) { 25 | OptionMapping idMapping = event.getOption("message-id"); 26 | if (idMapping == null || Checks.isInvalidLongInput(idMapping)) { 27 | Responses.replyMissingArguments(event).queue(); 28 | return; 29 | } 30 | if (event.getGuild() == null || event.getMember() == null) { 31 | Responses.replyGuildOnly(event).queue(); 32 | return; 33 | } 34 | if (!Checks.hasStaffRole(botConfig, event.getMember())) { 35 | Responses.replyStaffOnly(event, botConfig.get(event.getGuild())).queue(); 36 | return; 37 | } 38 | GuildMessageChannel channel = event.getOption("channel", event.getChannel().asGuildMessageChannel(), m -> m.getAsChannel().asGuildMessageChannel()); 39 | if (!event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_SEND, Permission.VIEW_CHANNEL)) { 40 | Responses.replyInsufficientPermissions(event, Permission.MESSAGE_SEND, Permission.VIEW_CHANNEL).queue(); 41 | return; 42 | } 43 | handleEmbedSubcommand(event, idMapping.getAsLong(), channel); 44 | } 45 | 46 | protected abstract void handleEmbedSubcommand(SlashCommandInteractionEvent event, long messageId, GuildMessageChannel channel); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/AvatarCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.util.Responses; 5 | import net.discordjug.javabot.util.UserUtils; 6 | import net.dv8tion.jda.api.EmbedBuilder; 7 | import net.dv8tion.jda.api.entities.MessageEmbed; 8 | import net.dv8tion.jda.api.entities.User; 9 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 10 | import net.dv8tion.jda.api.interactions.commands.OptionMapping; 11 | import net.dv8tion.jda.api.interactions.commands.OptionType; 12 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 13 | 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | /** 17 | * This class represents the `/avatar` command. 18 | */ 19 | public class AvatarCommand extends SlashCommand { 20 | /** 21 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 22 | */ 23 | public AvatarCommand() { 24 | setCommandData(Commands.slash("avatar", "Shows your or someone else's profile picture") 25 | .addOption(OptionType.USER, "user", "If given, shows the profile picture of the given user", false) 26 | .setGuildOnly(true) 27 | ); 28 | } 29 | 30 | @Override 31 | public void execute(@NotNull SlashCommandInteractionEvent event) { 32 | long userId = event.getOption("user", event.getUser().getIdLong(), OptionMapping::getAsLong); 33 | event.deferReply().queue(); 34 | event.getJDA().retrieveUserById(userId).queue( 35 | user -> event.getHook().sendMessageEmbeds(buildAvatarEmbed(user)).queue(), 36 | err -> Responses.error(event.getHook(), "Could not retrieve user with id: `%s`", userId).queue() 37 | ); 38 | } 39 | 40 | private @NotNull MessageEmbed buildAvatarEmbed(@NotNull User createdBy) { 41 | return new EmbedBuilder() 42 | .setColor(Responses.Type.DEFAULT.getColor()) 43 | .setAuthor(UserUtils.getUserTag(createdBy), null, createdBy.getEffectiveAvatarUrl()) 44 | .setTitle("Avatar") 45 | .setImage(createdBy.getEffectiveAvatarUrl() + "?size=4096") 46 | .build(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/notification/UserNotificationService.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.notification; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import net.discordjug.javabot.data.config.guild.ModerationConfig; 8 | import net.discordjug.javabot.util.UserUtils; 9 | import net.dv8tion.jda.api.entities.User; 10 | import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; 11 | import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel.AutoArchiveDuration; 12 | import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; 13 | import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; 14 | 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.util.function.Function; 18 | 19 | /** 20 | * Handles all sorts of user notifications. 21 | */ 22 | @Slf4j 23 | @RequiredArgsConstructor(access = AccessLevel.PACKAGE) 24 | @AllArgsConstructor(access = AccessLevel.PACKAGE) 25 | public final class UserNotificationService extends NotificationService.MessageChannelNotification { 26 | private final User user; 27 | private ModerationConfig config; 28 | 29 | /** 30 | * Sends a notification to a {@link User}s' {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel}. 31 | * 32 | * @param function The {@link Function} to use which MUST return a {@link MessageCreateAction}. 33 | */ 34 | public void sendDirectMessage(@NotNull Function function) { 35 | user.openPrivateChannel().flatMap(function::apply).queue( 36 | msg -> {}, 37 | error -> { 38 | log.error("Could not open PrivateChannel with user " + UserUtils.getUserTag(user), error); 39 | if(config != null) { 40 | TextChannel container = config.getNotificationThreadChannel(); 41 | if(container != null) { 42 | container 43 | .createThreadChannel("JavaBot notification", true) 44 | .setAutoArchiveDuration(AutoArchiveDuration.TIME_1_HOUR) 45 | .queue(c -> { 46 | c.addThreadMember(user).queue(); 47 | send(c, function); 48 | }); 49 | } 50 | } 51 | } 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/search/SearchWebMessageContext.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands.search; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; 4 | import net.discordjug.javabot.data.config.SystemsConfig; 5 | import net.discordjug.javabot.util.ExceptionLogger; 6 | import net.discordjug.javabot.util.Responses; 7 | import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; 8 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 9 | 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | *

This class represents the "Search the Web" Message Context Menu command.

16 | * This Context Command allows members to search the internet using the Bing API. 17 | */ 18 | public class SearchWebMessageContext extends ContextCommand.Message { 19 | private final SystemsConfig systemsConfig; 20 | 21 | /** 22 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.CommandData}. 23 | * @param systemsConfig Configuration for various systems 24 | */ 25 | public SearchWebMessageContext(SystemsConfig systemsConfig) { 26 | this.systemsConfig = systemsConfig; 27 | setCommandData(Commands.message("Search the Web") 28 | .setGuildOnly(true) 29 | ); 30 | } 31 | 32 | @Override 33 | public void execute(@NotNull MessageContextInteractionEvent event) { 34 | String query = event.getTarget().getContentDisplay(); 35 | if (query.isEmpty() || query.isBlank()) { 36 | Responses.warning(event, "No Content", "The message doesn't have any content to search for").queue(); 37 | return; 38 | } 39 | event.deferReply().queue(); 40 | SearchWebService service = new SearchWebService(systemsConfig); 41 | try { 42 | event.getHook().sendMessageEmbeds(service.buildSearchWebEmbed(query)).queue(); 43 | } catch (IOException e) { 44 | ExceptionLogger.capture(e, getClass().getSimpleName()); 45 | Responses.warning(event.getHook(), "No Results", "There were no results for your search. " + 46 | "This might be due to safe-search or because your search was too complex. Please try again.").queue(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/custom_vc/CustomVCRepository.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.custom_vc; 2 | 3 | import java.util.List; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.jdbc.core.JdbcTemplate; 7 | import org.springframework.stereotype.Repository; 8 | 9 | /** 10 | * Stores currently active custom voice channels and the owners of these channels. 11 | */ 12 | @Repository 13 | @RequiredArgsConstructor 14 | public class CustomVCRepository { 15 | 16 | private final JdbcTemplate jdbcTemplate; 17 | 18 | /** 19 | * Stores a new custom voice channel. 20 | * @param id the channel ID 21 | * @param ownerId the ID of the owner of the voice channel 22 | */ 23 | public void addCustomVoiceChannel(long id, long ownerId) { 24 | jdbcTemplate.update("INSERT INTO custom_vc (channel_id, owner_id) VALUES (?, ?)", 25 | id, ownerId); 26 | } 27 | 28 | /** 29 | * Removes a custom voice channel. 30 | * @param id the channel ID 31 | */ 32 | public void removeCustomVoiceChannel(long id) { 33 | jdbcTemplate.update("DELETE FROM custom_vc WHERE channel_id = ?", id); 34 | } 35 | 36 | /** 37 | * Checks whether a channel is a custom voice channel. 38 | * @param id the channel ID 39 | * @return {@code true} if the channel is a custom voice channel, else {@code false} 40 | */ 41 | public boolean isCustomVoiceChannel(long id) { 42 | return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM custom_vc WHERE channel_id = ?", (rs, rowId) -> rs.getInt(1), id) > 0; 43 | } 44 | 45 | /** 46 | * Gets the owner of a custom voice channel. 47 | * @param voiceChannelId the ID of the voice channel 48 | * @return the ID of the owner of the custom voice channel 49 | */ 50 | public long getOwnerId(long voiceChannelId) { 51 | return jdbcTemplate.queryForObject("SELECT owner_id FROM custom_vc WHERE channel_id = ?", 52 | (rs, rowId) -> rs.getLong(1), 53 | voiceChannelId); 54 | } 55 | 56 | /** 57 | * Gets all custom voice channels of all guilds. 58 | * @return a {@link List} of all custom voice channel IDs 59 | */ 60 | public List getAllCustomVoiceChannels() { 61 | return jdbcTemplate.query("SELECT channel_id FROM custom_vc", (rs, row) -> rs.getLong(1)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/ModerateCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import lombok.RequiredArgsConstructor; 5 | import net.discordjug.javabot.data.config.BotConfig; 6 | import net.discordjug.javabot.util.Checks; 7 | import net.discordjug.javabot.util.Responses; 8 | import net.dv8tion.jda.api.entities.Member; 9 | import net.dv8tion.jda.api.entities.channel.ChannelType; 10 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 11 | import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | /** 16 | * Abstract class that represents a single moderation command. 17 | */ 18 | @RequiredArgsConstructor 19 | public abstract class ModerateCommand extends SlashCommand implements CommandModerationPermissions { 20 | /** 21 | * The main configuration of the bot. 22 | */ 23 | protected final BotConfig botConfig; 24 | private boolean requireStaff = true; 25 | 26 | @Override 27 | public void execute(@NotNull SlashCommandInteractionEvent event) { 28 | if (event.getGuild() == null) { 29 | Responses.replyGuildOnly(event).queue(); 30 | return; 31 | } 32 | Member member = event.getMember(); 33 | if (member == null) { 34 | Responses.replyMissingMember(event).queue(); 35 | return; 36 | } 37 | if (requireStaff && !Checks.hasStaffRole(botConfig, member)) { 38 | Responses.replyStaffOnly(event, botConfig.get(event.getGuild())).queue(); 39 | return; 40 | } 41 | if (event.getChannelType() != ChannelType.TEXT && event.getChannelType() != ChannelType.VOICE && !event.getChannelType().isThread()) { 42 | Responses.error(event, "This command can only be performed in a server text channel or thread.").queue(); 43 | return; 44 | } 45 | handleModerationCommand(event, member).queue(); 46 | } 47 | 48 | protected void setRequireStaff(boolean requireStaff) { 49 | this.requireStaff = requireStaff; 50 | } 51 | 52 | protected boolean isRequireStaff() { 53 | return requireStaff; 54 | } 55 | 56 | protected abstract ReplyCallbackAction handleModerationCommand(@NotNull SlashCommandInteractionEvent event, @NotNull Member moderator); 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/user_profile/model/HelpAccountData.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes.user_profile.model; 2 | 3 | import lombok.Data; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.systems.help.model.HelpAccount; 6 | import net.discordjug.javabot.util.ColorUtils; 7 | import net.discordjug.javabot.util.Pair; 8 | import net.dv8tion.jda.api.entities.Guild; 9 | import net.dv8tion.jda.api.entities.Role; 10 | 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | /** 14 | * API-Data class which contains all necessary information about the users' 15 | * help experience. 16 | */ 17 | @Data 18 | public class HelpAccountData { 19 | private String currentRank; 20 | private String currentRankColor; 21 | private String nextRank; 22 | private String nextRankColor; 23 | // Experience 24 | private double experienceCurrent; 25 | private double experiencePrevious; 26 | private double experienceNext; 27 | 28 | /** 29 | * A simple utility method which creates an instance of this class based on 30 | * the specified {@link HelpAccount}. 31 | * 32 | * @param botConfig configuration of the bot. 33 | * @param account The {@link HelpAccount} to convert. 34 | * @param guild The {@link Guild}. 35 | * @return An instance of the {@link HelpAccountData} class. 36 | */ 37 | public static @NotNull HelpAccountData of(BotConfig botConfig, @NotNull HelpAccount account, Guild guild) { 38 | HelpAccountData data = new HelpAccountData(); 39 | data.setExperienceCurrent(account.getExperience()); 40 | Pair previousRank = account.getPreviousExperienceGoal(botConfig, guild); 41 | if (previousRank != null && previousRank.first() != null) { 42 | data.setCurrentRank(previousRank.first().getName()); 43 | data.setCurrentRankColor(ColorUtils.toString(previousRank.first().getColor())); 44 | data.setExperiencePrevious(previousRank.second()); 45 | } 46 | Pair nextRank = account.getNextExperienceGoal(botConfig, guild); 47 | if (nextRank != null && nextRank.first() != null) { 48 | data.setNextRank(nextRank.first().getName()); 49 | data.setNextRankColor(ColorUtils.toString(nextRank.first().getColor())); 50 | data.setExperienceNext(nextRank.second()); 51 | } 52 | return data; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/tasks/MetricsUpdater.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.tasks; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import net.discordjug.javabot.annotations.PreRegisteredListener; 6 | import net.discordjug.javabot.data.config.BotConfig; 7 | import net.discordjug.javabot.data.config.guild.MetricsConfig; 8 | import net.discordjug.javabot.util.ExceptionLogger; 9 | import net.dv8tion.jda.api.entities.Guild; 10 | import net.dv8tion.jda.api.events.session.ReadyEvent; 11 | import net.dv8tion.jda.api.hooks.ListenerAdapter; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.Map; 16 | import java.util.concurrent.ScheduledExecutorService; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.function.Function; 19 | 20 | /** 21 | * Periodically updates the Stats Categories for each guild in a set interval. 22 | *

23 | * This updater should be added as an event listener to the bot, so that it 24 | * will automatically begin operation when the bot gives the ready event. 25 | *

26 | */ 27 | @Slf4j 28 | @RequiredArgsConstructor 29 | @PreRegisteredListener 30 | public class MetricsUpdater extends ListenerAdapter { 31 | private static final Map> TEXT_VARIABLES = Map.of( 32 | "{!member_count}", g -> String.valueOf(g.getMemberCount()), 33 | "{!server_name}", Guild::getName 34 | ); 35 | 36 | private final ScheduledExecutorService asyncPool; 37 | private final BotConfig botConfig; 38 | 39 | @Override 40 | public void onReady(@NotNull ReadyEvent event) { 41 | asyncPool.scheduleWithFixedDelay(() -> { 42 | for (Guild guild : event.getJDA().getGuilds()) { 43 | MetricsConfig config = botConfig.get(guild).getMetricsConfig(); 44 | if (config.getMetricsCategory() == null || config.getMetricsMessageTemplate().isEmpty()) { 45 | continue; 46 | } 47 | String text = config.getMetricsMessageTemplate(); 48 | for (Map.Entry> entry : TEXT_VARIABLES.entrySet()) { 49 | text = text.replace(entry.getKey(), entry.getValue().apply(guild)); 50 | } 51 | config.getMetricsCategory().getManager().setName(text).queue(s -> log.debug("Successfully updated Metrics"), t -> ExceptionLogger.capture(t, getClass().getSimpleName())); 52 | } 53 | }, 0, 20, TimeUnit.MINUTES); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/config/guild/QOTWConfig.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.config.guild; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import net.discordjug.javabot.data.config.GuildConfigItem; 6 | import net.dv8tion.jda.api.entities.Role; 7 | import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; 8 | import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel; 9 | import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; 10 | import net.dv8tion.jda.api.entities.channel.forums.ForumTag; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * Configuration for the guild's qotw system. 16 | */ 17 | @Data 18 | @EqualsAndHashCode(callSuper = true) 19 | public class QOTWConfig extends GuildConfigItem { 20 | private long questionChannelId; 21 | private long submissionChannelId; 22 | private long submissionsForumChannelId; 23 | private long questionRoleId; 24 | private long qotwReviewRoleId; 25 | private long qotwChampionRoleId; 26 | private long qotwSampleAnswerUserId; 27 | private String submissionForumOngoingReviewTagName = ""; 28 | 29 | public NewsChannel getQuestionChannel() { 30 | return this.getGuild().getNewsChannelById(this.questionChannelId); 31 | } 32 | 33 | public TextChannel getSubmissionChannel() { 34 | return this.getGuild().getTextChannelById(this.submissionChannelId); 35 | } 36 | 37 | public ForumChannel getSubmissionsForumChannel() { 38 | return this.getGuild().getForumChannelById(this.submissionsForumChannelId); 39 | } 40 | 41 | public Role getQOTWRole() { 42 | return this.getGuild().getRoleById(this.questionRoleId); 43 | } 44 | 45 | public Role getQOTWReviewRole() { 46 | return this.getGuild().getRoleById(this.qotwReviewRoleId); 47 | } 48 | 49 | public Role getQOTWChampionRole() { 50 | return this.getGuild().getRoleById(this.qotwChampionRoleId); 51 | } 52 | 53 | /** 54 | * Gets a {@link ForumTag} based on the specified name. 55 | * 56 | * @return The specified {@link ForumTag} or null if there is no tag matching the specified name. 57 | */ 58 | public ForumTag getSubmissionsForumOngoingReviewTag() { 59 | ForumChannel forumChannel = getSubmissionsForumChannel(); 60 | if (forumChannel == null) return null; 61 | List tags = forumChannel.getAvailableTagsByName(this.submissionForumOngoingReviewTagName, true); 62 | return tags.stream().findFirst().orElse(null); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/search/SearchWebCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands.search; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.data.config.SystemsConfig; 5 | import net.discordjug.javabot.util.ExceptionLogger; 6 | import net.discordjug.javabot.util.Responses; 7 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 8 | import net.dv8tion.jda.api.interactions.commands.OptionMapping; 9 | import net.dv8tion.jda.api.interactions.commands.OptionType; 10 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 11 | 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.io.IOException; 15 | 16 | /** 17 | *

This class represents the /search-web command.

18 | * This Command allows members to search the internet using the Bing API. 19 | */ 20 | public class SearchWebCommand extends SlashCommand { 21 | private final SystemsConfig systemsConfig; 22 | 23 | /** 24 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 25 | * @param systemsConfig Configuration for various systems 26 | */ 27 | public SearchWebCommand(SystemsConfig systemsConfig) { 28 | this.systemsConfig = systemsConfig; 29 | setCommandData(Commands.slash("search-web", "Searches the web by turning your text-input into a search query") 30 | .setGuildOnly(true) 31 | .addOption(OptionType.STRING, "query", "Text that will be converted into a search query", true) 32 | ); 33 | } 34 | 35 | @Override 36 | public void execute(@NotNull SlashCommandInteractionEvent event) { 37 | OptionMapping queryMapping = event.getOption("query"); 38 | if (queryMapping == null) { 39 | Responses.replyMissingArguments(event).queue(); 40 | return; 41 | } 42 | event.deferReply().queue(); 43 | SearchWebService service = new SearchWebService(systemsConfig); 44 | try { 45 | event.getHook().sendMessageEmbeds(service.buildSearchWebEmbed(queryMapping.getAsString())).queue(); 46 | } catch (IOException e) { 47 | ExceptionLogger.capture(e, getClass().getSimpleName()); 48 | Responses.warning(event.getHook(), "No Results", "There were no results for your search. " + 49 | "This might be due to safe-search or because your search was too complex. Please try again.").queue(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_preferences/dao/UserPreferenceRepository.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_preferences.dao; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | import java.util.Optional; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | import org.springframework.dao.DataAccessException; 9 | import org.springframework.dao.EmptyResultDataAccessException; 10 | import org.springframework.jdbc.core.JdbcTemplate; 11 | import org.springframework.stereotype.Repository; 12 | 13 | import lombok.RequiredArgsConstructor; 14 | import net.discordjug.javabot.systems.user_preferences.model.Preference; 15 | import net.discordjug.javabot.systems.user_preferences.model.UserPreference; 16 | 17 | /** 18 | * Dao class that represents the USER_PREFERENCES SQL Table. 19 | */ 20 | @Repository 21 | @RequiredArgsConstructor 22 | public class UserPreferenceRepository { 23 | private final JdbcTemplate jdbcTemplate; 24 | 25 | /** 26 | * Gets a specific preference value of a user. 27 | * @param userId the ID of the user 28 | * @param preference the preference to obtain the value from 29 | * @return An {@link Optional} containing the value of the preference 30 | * @throws DataAccessException if any error occured 31 | */ 32 | public Optional getById(long userId, @NotNull Preference preference) throws DataAccessException { 33 | try { 34 | return Optional.of(jdbcTemplate.queryForObject("SELECT * FROM USER_PREFERENCES WHERE user_id = ? AND ordinal = ?", (rs, row)->this.read(rs), 35 | userId, preference.ordinal())); 36 | }catch (EmptyResultDataAccessException e) { 37 | return Optional.empty(); 38 | } 39 | } 40 | 41 | public boolean updateState(long userId, @NotNull Preference preference, String state) throws DataAccessException { 42 | return jdbcTemplate.update("UPDATE USER_PREFERENCES SET state = ? WHERE user_id = ? AND ordinal = ?", 43 | state, userId, preference.ordinal()) > 0; 44 | } 45 | 46 | public void insert(UserPreference instance) throws DataAccessException { 47 | jdbcTemplate.update("INSERT INTO USER_PREFERENCES (user_id, ordinal, state) VALUES (?,?,?)", 48 | instance.getUserId(), instance.getPreference().ordinal(), instance.getState()); 49 | } 50 | 51 | private UserPreference read(ResultSet rs) throws SQLException { 52 | return new UserPreference(rs.getLong("user_id"),Preference.values()[rs.getInt("ordinal")],rs.getString("state")); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_commands/BotInfoCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.util.Constants; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.discordjug.javabot.util.StringUtils; 7 | import net.discordjug.javabot.util.UserUtils; 8 | import net.dv8tion.jda.api.EmbedBuilder; 9 | import net.dv8tion.jda.api.JDA; 10 | import net.dv8tion.jda.api.entities.MessageEmbed; 11 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 12 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 13 | import net.dv8tion.jda.api.interactions.components.buttons.Button; 14 | import net.dv8tion.jda.api.utils.MarkdownUtil; 15 | 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.time.Instant; 19 | 20 | /** 21 | * This class represents the `/botinfo` command. 22 | */ 23 | public class BotInfoCommand extends SlashCommand { 24 | /** 25 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 26 | */ 27 | public BotInfoCommand() { 28 | setCommandData(Commands.slash("botinfo", "Shows some information about the Bot.") 29 | .setGuildOnly(true) 30 | ); 31 | } 32 | 33 | @Override 34 | public void execute(@NotNull SlashCommandInteractionEvent event) { 35 | event.replyEmbeds(buildBotInfoEmbed(event.getJDA())) 36 | .addActionRow(Button.link(Constants.GITHUB_LINK, "View on GitHub")) 37 | .queue(); 38 | } 39 | 40 | private @NotNull MessageEmbed buildBotInfoEmbed(@NotNull JDA jda) { 41 | return new EmbedBuilder() 42 | .setColor(Responses.Type.DEFAULT.getColor()) 43 | .setThumbnail(jda.getSelfUser().getEffectiveAvatarUrl()) 44 | .setAuthor(UserUtils.getUserTag(jda.getSelfUser()), null, jda.getSelfUser().getEffectiveAvatarUrl()) 45 | .setTitle("Bot Information") 46 | .addField("OS", MarkdownUtil.codeblock(StringUtils.getOperatingSystem()), true) 47 | .addField("Uptime", MarkdownUtil.codeblock(StringUtils.formatUptime()), true) 48 | .addField("Library", MarkdownUtil.codeblock("JDA"), true) 49 | .addField("JDK", MarkdownUtil.codeblock(System.getProperty("java.version")), true) 50 | .addField("Gateway Ping", MarkdownUtil.codeblock(jda.getGatewayPing() + "ms"), true) 51 | .setTimestamp(Instant.now()) 52 | .build(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Build JavaBot 5 | 6 | on: [push, pull_request, workflow_dispatch] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Set up JDK 17 16 | uses: actions/setup-java@v4 17 | with: 18 | java-version: '17' 19 | distribution: 'temurin' 20 | - name: Grant execute permission for gradlew 21 | run: chmod +x gradlew 22 | - name: Build with Gradle 23 | run: ./gradlew build 24 | - name: Test with Gradle 25 | run: ./gradlew test 26 | publish: 27 | runs-on: ubuntu-latest 28 | permissions: 29 | contents: read 30 | needs: build 31 | if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' }} 32 | steps: 33 | - uses: actions/checkout@v4 34 | - name: Set up JDK 21 35 | uses: graalvm/setup-graalvm@v1 36 | with: 37 | java-version: '21' 38 | distribution: 'graalvm-community' 39 | - name: Build native-image 40 | run: ./gradlew nativeCompile -Pprod 41 | - name: Build Docker image 42 | run: docker build -t javabot . 43 | - name: Tag docker image 44 | run: | 45 | docker tag javabot ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPONAME }}:${{ github.sha }} 46 | docker tag javabot ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPONAME }}:latest 47 | env: 48 | dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} 49 | if: env.dockerhub_username != null 50 | - name: Login to DockerHub 51 | uses: docker/login-action@v3 52 | with: 53 | username: ${{ secrets.DOCKERHUB_USERNAME }} 54 | password: ${{ secrets.DOCKERHUB_TOKEN }} 55 | env: 56 | dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} 57 | if: env.dockerhub_username != null 58 | - name: Push to Docker Hub 59 | run: docker push --all-tags ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPONAME }} 60 | env: 61 | dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} 62 | if: env.dockerhub_username != null 63 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/custom_vc/commands/CustomVCRemoveMemberSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.custom_vc.commands; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.systems.custom_vc.CustomVCRepository; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.dv8tion.jda.api.Permission; 7 | import net.dv8tion.jda.api.entities.Member; 8 | import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; 9 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 10 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 11 | 12 | /** 13 | * Command for removing a member from a custom voice channel. 14 | * Only the owner of the custom voice channel can use this command. 15 | * This command ensures that the member in question is no longer in the custom voice channel and can no longer join that channel. 16 | */ 17 | public class CustomVCRemoveMemberSubcommand extends CustomVCChangeMembersSubcommand { 18 | 19 | public CustomVCRemoveMemberSubcommand(CustomVCRepository dataStorage, 20 | BotConfig botConfig) { 21 | super(new SubcommandData("remove-member", "removes a member to the voice channel"), dataStorage, botConfig); 22 | } 23 | 24 | @Override 25 | protected void apply(VoiceChannel vc, Member member, SlashCommandInteractionEvent event) { 26 | if (member.getRoles().contains(botConfig.get(event.getGuild()).getModerationConfig().getStaffRole())) { 27 | Responses.error(event, "Cannot remove staff members from custom voice channels.").queue(); 28 | return; 29 | } 30 | 31 | if (event.getMember().getIdLong() == member.getIdLong()) { 32 | Responses.error(event, "You cannot perform this action on yourself.").queue(); 33 | return; 34 | } 35 | 36 | if (member.getVoiceState().getChannel() != null && member.getVoiceState().getChannel().getIdLong() == vc.getIdLong()) { 37 | VoiceChannel afkChannel = event.getGuild().getAfkChannel(); 38 | if (afkChannel == null) { 39 | event.getGuild().kickVoiceMember(member).queue(); 40 | } else { 41 | event.getGuild().moveVoiceMember(member, afkChannel).queue(); 42 | } 43 | } 44 | 45 | vc.upsertPermissionOverride(member) 46 | .setDenied(Permission.VIEW_CHANNEL) 47 | .queue(); 48 | event 49 | .reply("Successfully removed " + member.getAsMention() + " from " + vc.getAsMention() + ". They can no longer join the voice channel.") 50 | .setEphemeral(true) 51 | .queue(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/BanCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.util.Checks; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.dv8tion.jda.api.Permission; 7 | import net.dv8tion.jda.api.entities.Member; 8 | import net.dv8tion.jda.api.entities.Message; 9 | import net.dv8tion.jda.api.entities.User; 10 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 11 | import net.dv8tion.jda.api.interactions.commands.OptionType; 12 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 13 | import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; 14 | 15 | import javax.annotation.Nonnull; 16 | import javax.annotation.Nullable; 17 | 18 | /** 19 | * Command that allows staff-members to ban guild members. 20 | */ 21 | public class BanCommand extends ModerateUserCommand { 22 | private final ModerationService moderationService; 23 | 24 | /** 25 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 26 | * @param botConfig The main configuration of the bot 27 | * @param moderationService Service object for moderating members 28 | */ 29 | public BanCommand(BotConfig botConfig, ModerationService moderationService) { 30 | super(botConfig); 31 | this.moderationService = moderationService; 32 | setModerationSlashCommandData(Commands.slash("ban", "Ban a user.") 33 | .addOption(OptionType.USER, "user", "The user to ban.", true) 34 | .addOption(OptionType.STRING, "reason", "The reason for banning this user.", true) 35 | .addOption(OptionType.BOOLEAN, "quiet", "If true, don't send a message in the server channel where the ban is issued.", false) 36 | ); 37 | } 38 | 39 | @Override 40 | protected WebhookMessageCreateAction handleModerationUserCommand(@Nonnull SlashCommandInteractionEvent event, @Nonnull Member commandUser, @Nonnull User target, @Nullable String reason) { 41 | if (!Checks.hasPermission(event.getGuild(), Permission.BAN_MEMBERS)) { 42 | return Responses.replyInsufficientPermissions(event.getHook(), Permission.BAN_MEMBERS); 43 | } 44 | boolean quiet = isQuiet(event); 45 | moderationService.ban(target, reason, commandUser, event.getChannel(), quiet); 46 | return Responses.success(event.getHook(), "User Banned", "%s has been banned.", target.getAsMention()); 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/KickCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.util.Checks; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.dv8tion.jda.api.Permission; 7 | import net.dv8tion.jda.api.entities.Member; 8 | import net.dv8tion.jda.api.entities.Message; 9 | import net.dv8tion.jda.api.entities.User; 10 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 11 | import net.dv8tion.jda.api.interactions.commands.OptionType; 12 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 13 | import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; 14 | 15 | import javax.annotation.Nonnull; 16 | import javax.annotation.Nullable; 17 | 18 | /** 19 | *

This class represents the /kick command.

20 | */ 21 | public class KickCommand extends ModerateUserCommand { 22 | private final ModerationService moderationService; 23 | 24 | /** 25 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 26 | * @param botConfig The main configuration of the bot 27 | * @param moderationService Service object for moderating members 28 | */ 29 | public KickCommand(BotConfig botConfig, ModerationService moderationService) { 30 | super(botConfig); 31 | this.moderationService = moderationService; 32 | setModerationSlashCommandData(Commands.slash("kick", "Kicks a member") 33 | .addOption(OptionType.USER, "user", "The user to kick.", true) 34 | .addOption(OptionType.STRING, "reason", "The reason for kicking this user.", true) 35 | .addOption(OptionType.BOOLEAN, "quiet", "If true, don't send a message in the server channel where the kick is issued.", false) 36 | ); 37 | } 38 | 39 | @Override 40 | protected WebhookMessageCreateAction handleModerationUserCommand(@Nonnull SlashCommandInteractionEvent event, @Nonnull Member commandUser, @Nonnull User target, @Nullable String reason) { 41 | if (!Checks.hasPermission(event.getGuild(), Permission.KICK_MEMBERS)) { 42 | return Responses.replyInsufficientPermissions(event.getHook(), Permission.KICK_MEMBERS); 43 | } 44 | boolean quiet = isQuiet(event); 45 | moderationService.kick(target, reason, event.getMember(), event.getChannel(), quiet); 46 | return Responses.success(event.getHook(), "User Kicked", "%s has been kicked.", target.getAsMention()); 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/TomcatConfig.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api; 2 | 3 | import org.apache.catalina.connector.Connector; 4 | import org.apache.coyote.ajp.AjpNioProtocol; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import java.net.InetAddress; 11 | 12 | import net.discordjug.javabot.data.config.SystemsConfig; 13 | 14 | 15 | /** 16 | * Holds all configuration for the {@link org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat} 17 | * web service. 18 | */ 19 | @Configuration 20 | public class TomcatConfig { 21 | 22 | private final int ajpPort; 23 | private final InetAddress ajpAddress; 24 | private final boolean tomcatAjpEnabled; 25 | private final SystemsConfig systemsConfig; 26 | 27 | /** 28 | * Initializes this object. 29 | * @param ajpPort The port to run AJP under 30 | * @param tomcatAjpEnabled true if AJP is enabled, else false 31 | * @param systemsConfig an object representing the configuration of various systems 32 | * @param ajpAddress the listen address for AJP 33 | */ 34 | public TomcatConfig(@Value("${tomcat.ajp.port}") int ajpPort, @Value("${tomcat.ajp.enabled}") boolean tomcatAjpEnabled, @Value("${tomcat.ajp.address}") InetAddress ajpAddress, SystemsConfig systemsConfig) { 35 | this.ajpPort = ajpPort; 36 | this.tomcatAjpEnabled = tomcatAjpEnabled; 37 | this.systemsConfig = systemsConfig; 38 | this.ajpAddress = ajpAddress; 39 | } 40 | 41 | /** 42 | * Sets up the {@link TomcatServletWebServerFactory} using the {@link Value}s defined in the 43 | * application.properties file. 44 | * 45 | * @return The {@link TomcatServletWebServerFactory}. 46 | */ 47 | @Bean 48 | TomcatServletWebServerFactory servletContainer() { 49 | TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); 50 | if (tomcatAjpEnabled) { 51 | Connector ajpConnector = new Connector("org.apache.coyote.ajp.AjpNioProtocol"); 52 | AjpNioProtocol protocol= (AjpNioProtocol) ajpConnector.getProtocolHandler(); 53 | protocol.setSecret(systemsConfig.getApiConfig().getAjpSecret()); 54 | protocol.setAddress(ajpAddress); 55 | ajpConnector.setPort(ajpPort); 56 | ajpConnector.setSecure(true); 57 | tomcat.addAdditionalTomcatConnectors(ajpConnector); 58 | } 59 | return tomcat; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/configuration/GetConfigSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.configuration; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.data.config.GuildConfig; 5 | import net.discordjug.javabot.data.config.UnknownPropertyException; 6 | import net.discordjug.javabot.util.Responses; 7 | import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; 8 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 9 | import net.dv8tion.jda.api.interactions.AutoCompleteQuery; 10 | import net.dv8tion.jda.api.interactions.commands.OptionMapping; 11 | import net.dv8tion.jda.api.interactions.commands.OptionType; 12 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 13 | import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; 14 | import xyz.dynxsty.dih4jda.interactions.AutoCompletable; 15 | 16 | import javax.annotation.Nonnull; 17 | 18 | /** 19 | * Subcommand that allows staff-members to get a single property variable from the guild config. 20 | */ 21 | public class GetConfigSubcommand extends ConfigSubcommand implements AutoCompletable { 22 | /** 23 | * The constructor of this class, which sets the corresponding {@link SubcommandData}. 24 | * @param botConfig The main configuration of the bot 25 | */ 26 | public GetConfigSubcommand(BotConfig botConfig) { 27 | super(botConfig); 28 | setCommandData(new SubcommandData("get", "Get the current value of a configuration property.") 29 | .addOption(OptionType.STRING, "property", "The name of a property.", true, true) 30 | ); 31 | } 32 | 33 | @Override 34 | public ReplyCallbackAction handleConfigSubcommand(@Nonnull SlashCommandInteractionEvent event, @Nonnull GuildConfig config) throws UnknownPropertyException { 35 | OptionMapping propertyOption = event.getOption("property"); 36 | if (propertyOption == null) { 37 | return Responses.replyMissingArguments(event); 38 | } 39 | String property = propertyOption.getAsString().trim(); 40 | Object value = config.resolve(property); 41 | return Responses.info(event, "Configuration Property", "The value of the property `%s` is:\n```\n%s\n```", property, value); 42 | } 43 | 44 | @Override 45 | public void handleAutoComplete(CommandAutoCompleteInteractionEvent event, AutoCompleteQuery target) { 46 | if (target.getName().equals("property")) { 47 | String partialText = target.getValue(); 48 | handlePropertyAutocomplete(event, partialText); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/warn/DiscardWarnByIdSubCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.warn; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.systems.moderation.ModerationService; 6 | import net.discordjug.javabot.util.Checks; 7 | import net.discordjug.javabot.util.Responses; 8 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 9 | import net.dv8tion.jda.api.interactions.commands.OptionMapping; 10 | import net.dv8tion.jda.api.interactions.commands.OptionType; 11 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | /** 16 | *

This class represents the /warn discard-by-id command.

17 | */ 18 | public class DiscardWarnByIdSubCommand extends SlashCommand.Subcommand { 19 | private final BotConfig botConfig; 20 | private final ModerationService moderationService; 21 | 22 | /** 23 | * The constructor of this class, which sets the corresponding {@link SubcommandData}. 24 | * @param botConfig The main configuration of the bot 25 | * @param moderationService Service object for moderating members 26 | */ 27 | public DiscardWarnByIdSubCommand(BotConfig botConfig, ModerationService moderationService) { 28 | this.botConfig = botConfig; 29 | this.moderationService = moderationService; 30 | setCommandData(new SubcommandData("discard-by-id", "Discards a single warn, based on its id.") 31 | .addOption(OptionType.INTEGER, "id", "The warn's unique identifier.", true) 32 | ); 33 | } 34 | 35 | @Override 36 | public void execute(@NotNull SlashCommandInteractionEvent event) { 37 | OptionMapping idMapping = event.getOption("id"); 38 | if (idMapping == null) { 39 | Responses.replyMissingArguments(event).queue(); 40 | return; 41 | } 42 | if (event.getGuild() == null) { 43 | Responses.replyGuildOnly(event).queue(); 44 | return; 45 | } 46 | if(!Checks.hasStaffRole(botConfig, event.getMember())) { 47 | Responses.replyStaffOnly(event, botConfig.get(event.getGuild())).queue(); 48 | return; 49 | } 50 | int id = idMapping.getAsInt(); 51 | if (moderationService.discardWarnById(id, event.getMember())) { 52 | Responses.success(event, "Warn Discarded", "Successfully discarded the specified warn with id `%s`", id).queue(); 53 | } else { 54 | Responses.error(event, "Could not find and/or discard warn with id `%s`", id).queue(); 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWChampionJob.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.jobs; 2 | 3 | import java.time.LocalDate; 4 | 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Service; 7 | 8 | import lombok.RequiredArgsConstructor; 9 | import net.discordjug.javabot.data.config.BotConfig; 10 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; 11 | import net.discordjug.javabot.systems.qotw.dao.QOTWChampionRepository; 12 | import net.discordjug.javabot.systems.qotw.dao.QuestionPointsRepository; 13 | import net.dv8tion.jda.api.JDA; 14 | import net.dv8tion.jda.api.entities.Guild; 15 | import net.dv8tion.jda.api.entities.Member; 16 | import net.dv8tion.jda.api.entities.Role; 17 | 18 | /** 19 | * Jobs which gives the QOTW Champion role to the users with the highest monthly QOTW score. 20 | */ 21 | @RequiredArgsConstructor 22 | @Service 23 | public class QOTWChampionJob { 24 | 25 | private final BotConfig botConfig; 26 | private final QOTWPointsService pointsService; 27 | private final QuestionPointsRepository pointsRepository; 28 | private final QOTWChampionRepository qotwChampionRepository; 29 | private final JDA jda; 30 | 31 | /** 32 | * Gives the QOTW Champion role to the users with the highest monthly QOTW score. 33 | */ 34 | @Scheduled(cron = "0 0 0 1 * *") //start of month 35 | public void execute() { 36 | for (Guild guild : jda.getGuilds()) { 37 | LocalDate month=LocalDate.now().minusMonths(1); 38 | Role qotwChampionRole = botConfig.get(guild).getQotwConfig().getQOTWChampionRole(); 39 | if (qotwChampionRole != null) { 40 | pointsRepository.getTopAccounts(month, 1, 1) 41 | .stream() 42 | .findFirst() 43 | .ifPresent(best -> { 44 | guild 45 | .retrieveMembersByIds(qotwChampionRepository.getCurrentQOTWChampions(guild.getIdLong())) 46 | .onSuccess(membersToRemove -> { 47 | for (Member member : membersToRemove) { 48 | if (pointsService.getOrCreateAccount(member.getIdLong()).getPoints() < best.getPoints()) { 49 | member.getGuild().removeRoleFromMember(member, qotwChampionRole).queue(); 50 | } 51 | } 52 | guild 53 | .retrieveMembersByIds( 54 | pointsRepository.getUsersWithSpecificScore(month, best.getPoints()) 55 | ).onSuccess(membersToAdd -> { 56 | for (Member member : membersToAdd) { 57 | guild.addRoleToMember(member, qotwChampionRole).queue(); 58 | } 59 | }); 60 | }); 61 | }); 62 | } 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/data/h2db/commands/DbAdminCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.data.h2db.commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.RegistrationType; 4 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 5 | import net.discordjug.javabot.data.config.BotConfig; 6 | import net.dv8tion.jda.api.Permission; 7 | import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; 8 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 9 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData; 10 | 11 | /** 12 | * Represents the `/db-admin` command. This holds administrative commands for managing the bot's database. 13 | */ 14 | public class DbAdminCommand extends SlashCommand { 15 | /** 16 | * This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and 17 | * adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.SubcommandGroup}s & {@link net.dv8tion.jda.api.interactions.commands.Command.Subcommand}s. 18 | * @param botConfig The main configuration of the bot 19 | * @param exportSchemaSubcommand /db-admin export-schema 20 | * @param exportTableSubcommand /db-admin export-table 21 | * @param migrationsListSubcommand /db-admin migrations-list 22 | * @param migrateSubcommand /db-admin migrate 23 | * @param quickMigrateSubcommand /db-admin quick-migrate 24 | * @param messageCacheInfoSubcommand /db-admin message-cache info 25 | */ 26 | public DbAdminCommand(BotConfig botConfig, ExportSchemaSubcommand exportSchemaSubcommand, ExportTableSubcommand exportTableSubcommand, MigrationsListSubcommand migrationsListSubcommand, MigrateSubcommand migrateSubcommand, QuickMigrateSubcommand quickMigrateSubcommand, MessageCacheInfoSubcommand messageCacheInfoSubcommand) { 27 | setRegistrationType(RegistrationType.GUILD); 28 | setCommandData(Commands.slash("db-admin", "(ADMIN ONLY) Administrative Commands for managing the bot's database.") 29 | .setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER)) 30 | .setGuildOnly(true) 31 | ); 32 | addSubcommands(exportSchemaSubcommand, exportTableSubcommand, migrationsListSubcommand, migrateSubcommand, quickMigrateSubcommand); 33 | addSubcommandGroups(SubcommandGroup.of( 34 | new SubcommandGroupData("message-cache", "Administrative tools for managing the Message Cache."), messageCacheInfoSubcommand 35 | )); 36 | setRequiredUsers(botConfig.getSystems().getAdminConfig().getAdminUsers()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/warn/DiscardAllWarnsSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation.warn; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.data.config.BotConfig; 5 | import net.discordjug.javabot.systems.moderation.ModerationService; 6 | import net.discordjug.javabot.util.Checks; 7 | import net.discordjug.javabot.util.Responses; 8 | import net.discordjug.javabot.util.UserUtils; 9 | import net.dv8tion.jda.api.entities.User; 10 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 11 | import net.dv8tion.jda.api.interactions.commands.OptionMapping; 12 | import net.dv8tion.jda.api.interactions.commands.OptionType; 13 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 14 | 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | /** 18 | *

This class represents the /warn discard-all command.

19 | */ 20 | public class DiscardAllWarnsSubcommand extends SlashCommand.Subcommand { 21 | private final BotConfig botConfig; 22 | private final ModerationService moderationService; 23 | 24 | /** 25 | * The constructor of this class, which sets the corresponding {@link SubcommandData}. 26 | * @param botConfig The main configuration of the bot 27 | * @param moderationService Service object for moderating members 28 | */ 29 | public DiscardAllWarnsSubcommand(BotConfig botConfig, ModerationService moderationService) { 30 | this.botConfig = botConfig; 31 | this.moderationService = moderationService; 32 | setCommandData(new SubcommandData("discard-all", "Discards all warns from a single user.") 33 | .addOption(OptionType.USER, "user", "The user which warns should be discarded.", true) 34 | ); 35 | } 36 | 37 | @Override 38 | public void execute(@NotNull SlashCommandInteractionEvent event) { 39 | OptionMapping userMapping = event.getOption("user"); 40 | if (userMapping == null) { 41 | Responses.error(event, "Please provide a valid user.").queue(); 42 | return; 43 | } 44 | if (event.getGuild() == null) { 45 | Responses.replyGuildOnly(event).queue(); 46 | return; 47 | } 48 | if(!Checks.hasStaffRole(botConfig, event.getMember())) { 49 | Responses.replyStaffOnly(event, botConfig.get(event.getGuild())).queue(); 50 | return; 51 | } 52 | User target = userMapping.getAsUser(); 53 | moderationService.discardAllWarns(target, event.getMember()); 54 | Responses.success(event, "Warns Discarded", "Successfully discarded all warns from **%s**.", UserUtils.getUserTag(target)).queue(); 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/user_preferences/commands/PreferencesListSubcommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.user_preferences.commands; 2 | 3 | import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; 4 | import net.discordjug.javabot.systems.user_preferences.UserPreferenceService; 5 | import net.discordjug.javabot.systems.user_preferences.model.Preference; 6 | import net.discordjug.javabot.util.Responses; 7 | import net.discordjug.javabot.util.UserUtils; 8 | import net.dv8tion.jda.api.EmbedBuilder; 9 | import net.dv8tion.jda.api.entities.MessageEmbed; 10 | import net.dv8tion.jda.api.entities.User; 11 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 12 | import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; 13 | 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | /** 17 | *

This class represents the /preferences list command.

18 | */ 19 | public class PreferencesListSubcommand extends SlashCommand.Subcommand { 20 | private final UserPreferenceService service; 21 | 22 | /** 23 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 24 | * @param service The {@link UserPreferenceService} 25 | */ 26 | public PreferencesListSubcommand(UserPreferenceService service) { 27 | this.service = service; 28 | setCommandData(new SubcommandData("list", "Shows all your preferences.")); 29 | } 30 | 31 | @Override 32 | public void execute(@NotNull SlashCommandInteractionEvent event) { 33 | event.replyEmbeds(buildPreferencesEmbed(service, event.getUser())) 34 | .setEphemeral(true) 35 | .queue(); 36 | } 37 | 38 | private @NotNull MessageEmbed buildPreferencesEmbed(UserPreferenceService service, @NotNull User user) { 39 | EmbedBuilder builder = new EmbedBuilder() 40 | .setAuthor(UserUtils.getUserTag(user), null, user.getEffectiveAvatarUrl()) 41 | .setTitle(user.getName() + "'s Preferences") 42 | .setColor(Responses.Type.INFO.getColor()); 43 | for (Preference p : Preference.values()) { 44 | builder.addField(buildPreferenceField(user, service, p)); 45 | } 46 | return builder.build(); 47 | } 48 | 49 | private MessageEmbed.@NotNull Field buildPreferenceField(@NotNull User user, @NotNull UserPreferenceService service, Preference preference) { 50 | String state = service.getOrCreate(user.getIdLong(), preference).getState(); 51 | return new MessageEmbed.Field(preference.toString(), state.isEmpty() ? String.format("`%s` has not yet been set.", preference) : state, true); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/notification/QOTWGuildNotificationService.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.notification; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import net.discordjug.javabot.systems.qotw.model.QOTWSubmission; 7 | import net.discordjug.javabot.systems.qotw.submissions.SubmissionStatus; 8 | import net.discordjug.javabot.util.UserUtils; 9 | import net.dv8tion.jda.api.EmbedBuilder; 10 | import net.dv8tion.jda.api.entities.Guild; 11 | import net.dv8tion.jda.api.entities.MessageEmbed; 12 | import net.dv8tion.jda.api.entities.User; 13 | import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; 14 | 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.time.Instant; 18 | 19 | /** 20 | * Handles all sorts of guild qotw notifications. 21 | */ 22 | @Slf4j 23 | @RequiredArgsConstructor(access = AccessLevel.PACKAGE) 24 | public class QOTWGuildNotificationService { 25 | /** 26 | * The {@link NotificationService}. 27 | */ 28 | protected final NotificationService notificationService; 29 | private final Guild guild; 30 | 31 | /** 32 | * Sends the executed action, performed on a QOTW submission thread, to the {@link Guild}s log channel. 33 | * 34 | * @param reviewedBy The user which reviewed the QOTW submission thread. 35 | * @param submission The {@link QOTWSubmission}. 36 | * @param status The {@link SubmissionStatus}. 37 | */ 38 | public void sendSubmissionActionNotification(User reviewedBy, @NotNull QOTWSubmission submission, SubmissionStatus status) { 39 | submission.retrieveAuthor(author -> { 40 | notificationService.withGuild(guild).sendToModerationLog(c -> c.sendMessageEmbeds(buildSubmissionActionEmbed(author, submission.getThread(), reviewedBy, status))); 41 | log.info("{} {} {}'s QOTW Submission", UserUtils.getUserTag(reviewedBy), status.getVerb(), UserUtils.getUserTag(author)); 42 | }); 43 | } 44 | 45 | private @NotNull MessageEmbed buildSubmissionActionEmbed(@NotNull User author, ThreadChannel thread, @NotNull User reviewedBy, @NotNull SubmissionStatus status) { 46 | EmbedBuilder builder = new EmbedBuilder() 47 | .setAuthor(UserUtils.getUserTag(reviewedBy), null, reviewedBy.getEffectiveAvatarUrl()) 48 | .setTitle(String.format("%s %s %s's QOTW Submission", UserUtils.getUserTag(reviewedBy), status.getVerb(), UserUtils.getUserTag(author))) 49 | .setTimestamp(Instant.now()); 50 | if (thread != null) { 51 | builder.addField("Thread", thread.getAsMention(), true); 52 | } 53 | return builder.build(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/notification/NotificationService.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.notification; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import net.discordjug.javabot.data.config.BotConfig; 6 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; 7 | import net.dv8tion.jda.api.entities.Guild; 8 | import net.dv8tion.jda.api.entities.User; 9 | import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; 10 | import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; 11 | import org.jetbrains.annotations.Contract; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.function.Function; 16 | 17 | /** 18 | * Handles all types of guild & user notifications. 19 | */ 20 | @Service 21 | @RequiredArgsConstructor 22 | public class NotificationService { 23 | private final QOTWPointsService qotwPointsService; 24 | private final BotConfig botConfig; 25 | 26 | @Contract("_ -> new") 27 | public @NotNull GuildNotificationService withGuild(Guild guild) { 28 | return new GuildNotificationService(botConfig.get(guild)); 29 | } 30 | 31 | @Contract("_ -> new") 32 | public @NotNull UserNotificationService withUser(User user) { 33 | return new UserNotificationService(user); 34 | } 35 | 36 | @Contract("_ -> new") 37 | public @NotNull UserNotificationService withUser(User user, Guild guild) { 38 | return new UserNotificationService(user, botConfig.get(guild).getModerationConfig()); 39 | } 40 | 41 | public @NotNull QOTWGuildNotificationService withQOTW(Guild guild) { 42 | return new QOTWGuildNotificationService(this, guild); 43 | } 44 | 45 | public @NotNull QOTWNotificationService withQOTW(Guild guild, User user) { 46 | return new QOTWNotificationService(this, qotwPointsService, user, guild, botConfig.getSystems()); 47 | } 48 | 49 | /** 50 | * Abstract class which streamlines the logic of sending messages to a {@link MessageChannel}. 51 | */ 52 | @Slf4j 53 | abstract static class MessageChannelNotification { 54 | /** 55 | * Sends a single message to the specified {@link MessageChannel} using the 56 | * specified {@link Function}. 57 | * 58 | * @param channel The target {@link MessageChannel}. 59 | * @param function The {@link Function} which is used in order to send the message. 60 | */ 61 | protected void send(MessageChannel channel, @NotNull Function function) { 62 | function.apply(channel).queue(s -> {}, 63 | err -> log.error("Could not send message to channel \" " + channel.getName() + "\": ", err) 64 | ); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionInteractionManager.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.qotw.submissions; 2 | 3 | import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; 4 | import xyz.dynxsty.dih4jda.interactions.components.StringSelectMenuHandler; 5 | import xyz.dynxsty.dih4jda.util.ComponentIdBuilder; 6 | import xyz.dynxsty.dih4jda.interactions.components.ButtonHandler; 7 | 8 | import lombok.RequiredArgsConstructor; 9 | import net.discordjug.javabot.annotations.AutoDetectableComponentHandler; 10 | import net.discordjug.javabot.data.config.BotConfig; 11 | import net.discordjug.javabot.systems.notification.NotificationService; 12 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; 13 | import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository; 14 | import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; 15 | import net.dv8tion.jda.api.interactions.components.buttons.Button; 16 | 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.util.List; 20 | import java.util.concurrent.ExecutorService; 21 | 22 | /** 23 | * Handles all interactions regarding the QOTW Submission System. 24 | */ 25 | @AutoDetectableComponentHandler({"qotw-submission","qotw-submission-select"}) 26 | @RequiredArgsConstructor 27 | public class SubmissionInteractionManager implements ButtonHandler, StringSelectMenuHandler { 28 | private final QOTWPointsService pointsService; 29 | private final NotificationService notificationService; 30 | private final BotConfig botConfig; 31 | private final QuestionQueueRepository questionQueueRepository; 32 | private final ExecutorService asyncPool; 33 | 34 | @Override 35 | public void handleButton(@NotNull ButtonInteractionEvent event, Button button) { 36 | SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool); 37 | String[] id = ComponentIdBuilder.split(event.getComponentId()); 38 | switch (id[1]) { 39 | case "submit" -> manager.handleSubmission(event, Integer.parseInt(id[2])).queue(); 40 | case "delete" -> manager.handleThreadDeletion(event); 41 | } 42 | } 43 | 44 | @Override 45 | public void handleStringSelectMenu(@NotNull StringSelectInteractionEvent event, @NotNull List values) { 46 | SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool); 47 | String[] id = ComponentIdBuilder.split(event.getComponentId()); 48 | switch (id[1]) { 49 | case "review" -> manager.handleSelectReview(event, id[2]); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/api/routes/metrics/MetricsController.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.api.routes.metrics; 2 | 3 | import com.github.benmanes.caffeine.cache.Caffeine; 4 | 5 | import net.discordjug.javabot.api.exception.InvalidEntityIdException; 6 | import net.discordjug.javabot.api.routes.CaffeineCache; 7 | import net.discordjug.javabot.api.routes.metrics.model.MetricsData; 8 | import net.discordjug.javabot.data.config.BotConfig; 9 | import net.discordjug.javabot.data.config.guild.MetricsConfig; 10 | import net.dv8tion.jda.api.JDA; 11 | import net.dv8tion.jda.api.entities.Guild; 12 | 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | import java.util.concurrent.TimeUnit; 20 | 21 | /** 22 | * Handles all GET-Requests on the guilds/{guild_id}/metrics/ route. 23 | */ 24 | @RestController 25 | public class MetricsController extends CaffeineCache { 26 | private final JDA jda; 27 | private final BotConfig botConfig; 28 | 29 | /** 30 | * The constructor of this class which initializes the {@link Caffeine} cache. 31 | * 32 | * @param jda The {@link Autowired} {@link JDA} instance to use. 33 | * @param botConfig The main configuration of the bot 34 | */ 35 | public MetricsController(final JDA jda, BotConfig botConfig) { 36 | super(Caffeine.newBuilder() 37 | .expireAfterWrite(15, TimeUnit.MINUTES) 38 | .build() 39 | ); 40 | this.jda = jda; 41 | this.botConfig = botConfig; 42 | } 43 | 44 | /** 45 | * Serves metrics for the specified guild. 46 | * 47 | * @param guildId The guilds' id. 48 | * @return The {@link ResponseEntity}. 49 | */ 50 | @GetMapping("guilds/{guild_id}/metrics") 51 | public ResponseEntity getMetrics(@PathVariable("guild_id") long guildId) { 52 | Guild guild = jda.getGuildById(guildId); 53 | if (guild == null) { 54 | throw new InvalidEntityIdException(Guild.class, "You've provided an invalid guild id!"); 55 | } 56 | MetricsData data = getCache().getIfPresent(guild.getIdLong()); 57 | if (data == null) { 58 | data = new MetricsData(); 59 | data.setMemberCount(guild.getMemberCount()); 60 | data.setOnlineCount(guild.retrieveMetaData().complete().getApproximatePresences()); 61 | MetricsConfig config = botConfig.get(guild).getMetricsConfig(); 62 | data.setWeeklyMessages(config.getWeeklyMessages()); 63 | data.setActiveMembers(config.getActiveMembers()); 64 | getCache().put(guild.getIdLong(), data); 65 | } 66 | return new ResponseEntity<>(data, HttpStatus.OK); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/discordjug/javabot/systems/moderation/UnbanCommand.java: -------------------------------------------------------------------------------- 1 | package net.discordjug.javabot.systems.moderation; 2 | 3 | import net.discordjug.javabot.data.config.BotConfig; 4 | import net.discordjug.javabot.util.Checks; 5 | import net.discordjug.javabot.util.Responses; 6 | import net.dv8tion.jda.api.entities.Member; 7 | import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 8 | import net.dv8tion.jda.api.interactions.commands.OptionMapping; 9 | import net.dv8tion.jda.api.interactions.commands.OptionType; 10 | import net.dv8tion.jda.api.interactions.commands.build.Commands; 11 | import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | /** 16 | *

This class represents the /unban command.

17 | * This Command allows staff-members to unban users from the current guild by their id. 18 | */ 19 | public class UnbanCommand extends ModerateCommand { 20 | private final ModerationService moderationService; 21 | 22 | /** 23 | * The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}. 24 | * @param botConfig The main configuration of the bot 25 | * @param moderationService Service object for moderating members 26 | */ 27 | public UnbanCommand(BotConfig botConfig, ModerationService moderationService) { 28 | super(botConfig); 29 | this.moderationService = moderationService; 30 | setModerationSlashCommandData(Commands.slash("unban", "Unbans a member") 31 | .addOption(OptionType.STRING, "id", "The id of the user you want to unban", true) 32 | .addOption(OptionType.STRING, "reason", "The reason for unbanning this user", true) 33 | .addOption(OptionType.BOOLEAN, "quiet", "If true, don't send a message in the server channel where the unban is issued.", false) 34 | ); 35 | } 36 | 37 | @Override 38 | protected ReplyCallbackAction handleModerationCommand(@NotNull SlashCommandInteractionEvent event, @NotNull Member moderator) { 39 | OptionMapping idOption = event.getOption("id"); 40 | OptionMapping reasonOption = event.getOption("reason"); 41 | if (idOption == null || Checks.isInvalidLongInput(idOption) || reasonOption == null) { 42 | return Responses.replyMissingArguments(event); 43 | } 44 | long id = idOption.getAsLong(); 45 | boolean quiet = ModerateUserCommand.isQuiet(botConfig, event); 46 | if (moderationService.unban(id, reasonOption.getAsString(), event.getMember(), event.getChannel(), quiet)) { 47 | return Responses.success(event, "User Unbanned", "User with id `%s` has been unbanned.", id); 48 | } else { 49 | return Responses.warning(event, "Could not find banned User with id `%s`", id); 50 | } 51 | } 52 | } --------------------------------------------------------------------------------