From 333d638f9412b79420e204cf25e35cb276f8ed77 Mon Sep 17 00:00:00 2001 From: anthorob Date: Thu, 12 Sep 2024 10:50:14 +0000 Subject: [PATCH] feat: implement button, modal and emoji tasks --- .env.example | 8 +- .jpb/jpb-settings.xml | 6 - bot.properties.example | 8 +- build-bot.sh | 0 docker-compose.yml | 2 +- pom.xml | 43 ++- sql/netneutrality_jpa.sql | 109 +++---- .../bobert/netneutrality/NetNeutrality.java | 46 +-- .../command/AbstractAdminCommand.java | 26 ++ .../command/AbstractCommand.java | 9 +- .../command/AbstractMusicCommand.java | 23 +- .../command/{ICommand.java => Command.java} | 11 +- .../command/admin/BlacklistCommand.java | 81 +++--- .../command/admin/ReactionCommand.java | 137 +++++++++ .../command/basic/HelpCommand.java | 12 +- .../command/basic/PingCommand.java | 6 +- .../command/bot/ConfigureCommand.java | 35 +-- .../command/crypto/CryptoPriceCommand.java | 25 +- .../command/git/ReportIssueCommand.java | 67 +++++ .../command/music/BitconnectCommand.java | 12 +- .../command/music/ClearQueueCommand.java | 12 +- .../music/CurrentlyPlayingCommand.java | 26 +- .../command/music/JoinCommand.java | 51 +--- .../command/music/LeaveCommand.java | 15 +- .../command/music/PlayCommand.java | 17 +- .../command/music/QueueCommand.java | 92 +++--- .../command/music/ShuffleCommand.java | 8 +- .../command/music/SkipCommand.java | 12 +- .../command/music/StopCommand.java | 12 +- .../command/music/ViewQueueCommand.java | 19 +- .../command/music/VolumeCommand.java | 10 +- .../command/music/VoteSkipCommand.java | 12 +- .../context/MessageCommandContext.java | 141 +++++++++ .../context/MusicChannelContext.java | 66 +++++ .../context/SlashCommandContext.java | 88 ++++++ .../context/tasks/ButtonTaskContext.java | 70 +++++ .../context/tasks/EmojiTaskContext.java | 78 +++++ .../context/tasks/ModalTaskContext.java | 66 +++++ .../netneutrality/core/AsyncMessage.java | 97 +++++++ .../bobert/netneutrality/core/BotState.java | 12 +- .../core/GuildConfigManager.java | 2 +- .../netneutrality/core/GuildManager.java | 40 +-- .../bobert/netneutrality/core/LogHandler.java | 45 +-- .../command/CommandExecutionResult.java | 6 +- .../{ => core}/command/CommandManager.java | 61 ++-- .../core/{ => command}/CommandProcessor.java | 55 ++-- .../core/context/AbstractContext.java | 61 ++++ .../core/context/CommandContext.java | 52 +--- .../netneutrality/core/context/Context.java | 161 +++++++++++ .../core/context/MessageCommandContext.java | 192 ------------ .../core/context/SlashCommandContext.java | 180 ------------ .../core/context/TaskContext.java | 32 ++ .../core/db/DatabaseMigration.java | 34 +++ .../core/{ => db}/SessionManager.java | 10 +- .../core/tasks/AbstractPersistableTask.java | 39 +++ .../core/tasks/AbstractTask.java | 46 +++ .../core/tasks/PersistableTask.java | 14 + .../bobert/netneutrality/core/tasks/Task.java | 62 ++++ .../netneutrality/core/tasks/TaskManager.java | 273 ++++++++++++++++++ .../core/tasks/persistence/TaskFactory.java | 69 +++++ .../core/tasks/persistence/TaskRegistry.java | 101 +++++++ .../core/tasks/trait/Deletable.java | 13 + .../core/tasks/trait/NotAcknowledgeable.java | 12 + .../core/tasks/trait/WithChildren.java | 22 ++ .../core/tasks/trait/WithParent.java | 16 + .../{ => core}/thread/BaseThreadFactory.java | 2 +- .../{ => core}/thread/ThreadManager.java | 33 +-- .../netneutrality/db/AbstractController.java | 23 -- .../bobert/netneutrality/db/DBController.java | 38 --- .../me/bobert/netneutrality/db/GSettings.java | 4 +- .../bobert/netneutrality/db/Repository.java | 42 +++ .../db/controllers/DBBlacklist.java | 141 --------- .../db/controllers/DBMessage.java | 97 ------- .../netneutrality/db/entities/OBlacklist.java | 3 - .../db/entities/OLogMessage.java | 4 - .../netneutrality/db/entities/OTask.java | 60 ++++ .../netneutrality/db/entities/OWebUser.java | 86 ++++++ .../db/repository/BlacklistRepository.java | 160 ++++++++++ .../db/repository/MessageRepository.java | 121 ++++++++ .../db/repository/TaskRepository.java | 60 ++++ .../exceptions/CommandException.java | 24 -- .../exceptions/CommandExecutionException.java | 7 +- .../exceptions/ContextException.java | 24 ++ .../exceptions/TaskExecutionException.java | 15 + ...erJoinHandler.java => ServerJoinHook.java} | 2 +- .../interaction/ButtonInteractionHook.java | 22 ++ .../interaction/ModalInteractionHook.java | 22 ++ .../SlashCommandHook.java} | 15 +- .../hooks/message/EditHandler.java | 10 +- .../hooks/message/EmojiReactionHook.java | 22 ++ ...dHandler.java => MessageReceivedHook.java} | 21 +- .../hooks/message/ReactionHandler.java | 31 -- ...eEventHandler.java => VoiceEventHook.java} | 2 +- .../bobert/netneutrality/http/WebModule.java | 8 +- .../netneutrality/http/server/WebApi.java | 4 +- .../netneutrality/music/MusicManager.java | 99 +++---- .../netneutrality/music/QueueManager.java | 7 +- .../music/TrackLoadingHandler.java | 32 +- .../netneutrality/music/TrackScheduler.java | 85 ++++-- .../music/source/UdioAudioSourceManager.java | 2 +- .../netneutrality/tasks/ButtonTask.java | 43 +++ .../bobert/netneutrality/tasks/EmojiTask.java | 44 +++ .../bobert/netneutrality/tasks/ModalTask.java | 43 +++ .../tasks/emoji/EmojiReactionRoleTask.java | 55 ++++ .../tasks/git/ReportIssueTask.java | 97 +++++++ .../tasks/music/AddAllTracksTask.java | 66 +++++ .../tasks/music/CancelLoadTask.java | 45 +++ .../tasks/music/LoadPlaylistTask.java | 133 +++++++++ .../music/LoadTrackFromPlaylistTask.java | 62 ++++ .../thread/CleanupTaskThread.java | 24 ++ .../MusicPlayerAutoDisconnectThread.java | 8 +- .../utils/ChannelMessagePurger.java | 75 ++--- .../bobert/netneutrality/utils/JpaUtils.java | 38 +-- .../netneutrality/utils/MessageFunction.java | 109 ++++--- .../netneutrality/utils/MusicUtils.java | 78 ++++- .../bobert/netneutrality/utils/Settings.java | 2 +- ...{app.properties => application.properties} | 0 src/main/resources/hibernate.cfg.xml | 5 +- src/main/resources/persistable-tasks.json | 17 ++ .../source/UdioAudioSourceManagerTest.java | 4 +- .../netneutrality/utils/SettingsTest.java | 10 +- 121 files changed, 3893 insertions(+), 1561 deletions(-) delete mode 100644 .jpb/jpb-settings.xml mode change 100644 => 100755 build-bot.sh create mode 100644 src/main/java/me/bobert/netneutrality/command/AbstractAdminCommand.java rename src/main/java/me/bobert/netneutrality/command/{ICommand.java => Command.java} (73%) create mode 100644 src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java create mode 100644 src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java create mode 100644 src/main/java/me/bobert/netneutrality/context/MessageCommandContext.java create mode 100644 src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java create mode 100644 src/main/java/me/bobert/netneutrality/context/SlashCommandContext.java create mode 100644 src/main/java/me/bobert/netneutrality/context/tasks/ButtonTaskContext.java create mode 100644 src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java create mode 100644 src/main/java/me/bobert/netneutrality/context/tasks/ModalTaskContext.java create mode 100644 src/main/java/me/bobert/netneutrality/core/AsyncMessage.java rename src/main/java/me/bobert/netneutrality/{ => core}/command/CommandExecutionResult.java (89%) rename src/main/java/me/bobert/netneutrality/{ => core}/command/CommandManager.java (54%) rename src/main/java/me/bobert/netneutrality/core/{ => command}/CommandProcessor.java (64%) create mode 100644 src/main/java/me/bobert/netneutrality/core/context/AbstractContext.java create mode 100644 src/main/java/me/bobert/netneutrality/core/context/Context.java delete mode 100644 src/main/java/me/bobert/netneutrality/core/context/MessageCommandContext.java delete mode 100644 src/main/java/me/bobert/netneutrality/core/context/SlashCommandContext.java create mode 100644 src/main/java/me/bobert/netneutrality/core/context/TaskContext.java create mode 100644 src/main/java/me/bobert/netneutrality/core/db/DatabaseMigration.java rename src/main/java/me/bobert/netneutrality/core/{ => db}/SessionManager.java (86%) create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/AbstractPersistableTask.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/AbstractTask.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/Task.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/TaskManager.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskFactory.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskRegistry.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/trait/Deletable.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/trait/NotAcknowledgeable.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/trait/WithChildren.java create mode 100644 src/main/java/me/bobert/netneutrality/core/tasks/trait/WithParent.java rename src/main/java/me/bobert/netneutrality/{ => core}/thread/BaseThreadFactory.java (94%) rename src/main/java/me/bobert/netneutrality/{ => core}/thread/ThreadManager.java (50%) delete mode 100644 src/main/java/me/bobert/netneutrality/db/AbstractController.java delete mode 100644 src/main/java/me/bobert/netneutrality/db/DBController.java create mode 100644 src/main/java/me/bobert/netneutrality/db/Repository.java delete mode 100644 src/main/java/me/bobert/netneutrality/db/controllers/DBBlacklist.java delete mode 100644 src/main/java/me/bobert/netneutrality/db/controllers/DBMessage.java create mode 100644 src/main/java/me/bobert/netneutrality/db/entities/OTask.java create mode 100644 src/main/java/me/bobert/netneutrality/db/entities/OWebUser.java create mode 100644 src/main/java/me/bobert/netneutrality/db/repository/BlacklistRepository.java create mode 100644 src/main/java/me/bobert/netneutrality/db/repository/MessageRepository.java create mode 100644 src/main/java/me/bobert/netneutrality/db/repository/TaskRepository.java delete mode 100644 src/main/java/me/bobert/netneutrality/exceptions/CommandException.java create mode 100644 src/main/java/me/bobert/netneutrality/exceptions/ContextException.java create mode 100644 src/main/java/me/bobert/netneutrality/exceptions/TaskExecutionException.java rename src/main/java/me/bobert/netneutrality/hooks/guilds/{ServerJoinHandler.java => ServerJoinHook.java} (92%) create mode 100644 src/main/java/me/bobert/netneutrality/hooks/interaction/ButtonInteractionHook.java create mode 100644 src/main/java/me/bobert/netneutrality/hooks/interaction/ModalInteractionHook.java rename src/main/java/me/bobert/netneutrality/hooks/{SlashCommandHooks.java => interaction/SlashCommandHook.java} (54%) create mode 100644 src/main/java/me/bobert/netneutrality/hooks/message/EmojiReactionHook.java rename src/main/java/me/bobert/netneutrality/hooks/message/{MessageReceivedHandler.java => MessageReceivedHook.java} (52%) delete mode 100644 src/main/java/me/bobert/netneutrality/hooks/message/ReactionHandler.java rename src/main/java/me/bobert/netneutrality/hooks/voice/{VoiceEventHandler.java => VoiceEventHook.java} (87%) create mode 100644 src/main/java/me/bobert/netneutrality/tasks/ButtonTask.java create mode 100644 src/main/java/me/bobert/netneutrality/tasks/EmojiTask.java create mode 100644 src/main/java/me/bobert/netneutrality/tasks/ModalTask.java create mode 100644 src/main/java/me/bobert/netneutrality/tasks/emoji/EmojiReactionRoleTask.java create mode 100644 src/main/java/me/bobert/netneutrality/tasks/git/ReportIssueTask.java create mode 100644 src/main/java/me/bobert/netneutrality/tasks/music/AddAllTracksTask.java create mode 100644 src/main/java/me/bobert/netneutrality/tasks/music/CancelLoadTask.java create mode 100644 src/main/java/me/bobert/netneutrality/tasks/music/LoadPlaylistTask.java create mode 100644 src/main/java/me/bobert/netneutrality/tasks/music/LoadTrackFromPlaylistTask.java create mode 100644 src/main/java/me/bobert/netneutrality/thread/CleanupTaskThread.java rename src/main/resources/{app.properties => application.properties} (100%) create mode 100644 src/main/resources/persistable-tasks.json rename src/test/java/{ => me/bobert}/netneutrality/music/source/UdioAudioSourceManagerTest.java (86%) rename src/test/java/{ => me/bobert}/netneutrality/utils/SettingsTest.java (58%) diff --git a/.env.example b/.env.example index 81b3a9a..cfd38d0 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,10 @@ MYSQL_PASSWORD= MYSQL_DATABASE= MYSQL_PORT=3306 -NETNEUTRALITY_SENTRY_DSN= \ No newline at end of file +NETNEUTRALITY_PO_TOKEN= +NETNEUTRALITY_VISITOR_DATA= + +NETNEUTRALITY_SENTRY_DSN= +GITLAB_HOST= +GITLAB_TOKEN= +GITLAB_PROJECT_ID= \ No newline at end of file diff --git a/.jpb/jpb-settings.xml b/.jpb/jpb-settings.xml deleted file mode 100644 index 935cf0d..0000000 --- a/.jpb/jpb-settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/bot.properties.example b/bot.properties.example index fe1be76..895405b 100644 --- a/bot.properties.example +++ b/bot.properties.example @@ -10,4 +10,10 @@ MYSQL_PASSWORD= MYSQL_DATABASE= MYSQL_PORT=3306 -NETNEUTRALITY_SENTRY_DSN= \ No newline at end of file +NETNEUTRALITY_PO_TOKEN= +NETNEUTRALITY_VISITOR_DATA= + +NETNEUTRALITY_SENTRY_DSN= +GITLAB_HOST= +GITLAB_TOKEN= +GITLAB_PROJECT_ID= \ No newline at end of file diff --git a/build-bot.sh b/build-bot.sh old mode 100644 new mode 100755 diff --git a/docker-compose.yml b/docker-compose.yml index 43dc581..ec767f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: MYSQL_ROOT_PASSWORD: 'password' MARIADB_AUTO_UPGRADE: '1' ports: - - "3306" + - "3306:3306" networks: - internal diff --git a/pom.xml b/pom.xml index 8a2db6d..31901f5 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,16 @@ + + org.flywaydb + flyway-maven-plugin + 10.17.0 + + jdbc:mysql://localhost:3306/netneutrality + root + + + org.apache.maven.plugins maven-resources-plugin @@ -91,17 +101,17 @@ net.dv8tion JDA - 5.0.0 + 5.1.0 dev.arbjerg lavaplayer - 2.1.2 + 2.2.1 dev.lavalink.youtube v2 - 1.4.0 + 1.7.2 @@ -113,23 +123,38 @@ org.hibernate.orm hibernate-core - 6.1.7.Final + 6.5.2.Final org.hibernate.orm hibernate-hikaricp - 6.1.7.Final + 6.5.2.Final com.mysql mysql-connector-j - 8.3.0 + 9.0.0 + + + org.flywaydb + flyway-core + 10.17.0 + + + org.flywaydb + flyway-mysql + 10.17.0 io.sentry sentry - 7.6.0 + 7.12.1 + + + org.gitlab4j + gitlab4j-api + 6.0.0-rc.5 @@ -163,12 +188,12 @@ org.apache.commons commons-lang3 - 3.12.0 + 3.15.0 commons-codec commons-codec - 1.15 + 1.17.0 commons-io diff --git a/sql/netneutrality_jpa.sql b/sql/netneutrality_jpa.sql index 61976ad..3ccf4f8 100644 --- a/sql/netneutrality_jpa.sql +++ b/sql/netneutrality_jpa.sql @@ -11,9 +11,9 @@ SET time_zone = "+00:00"; -- -- Database: `netneutrality` -- -CREATE DATABASE IF NOT EXISTS netneutrality CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS `netneutrality` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE `netneutrality`; -USE netneutrality; -- -------------------------------------------------------- -- @@ -22,13 +22,13 @@ USE netneutrality; DROP TABLE IF EXISTS `blacklist`; CREATE TABLE IF NOT EXISTS `blacklist` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `word` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `enabled` tinyint(1) NOT NULL, - `creator_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - PRIMARY KEY (`id`), - KEY `fk_blacklist_guilds` (`guild_id`) + `id` int(11) NOT NULL AUTO_INCREMENT, + `guild_id` varchar(255) NOT NULL, + `word` varchar(255) NOT NULL, + `enabled` tinyint(1) NOT NULL, + `creator_id` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_blacklist_guilds` (`guild_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -------------------------------------------------------- @@ -39,11 +39,11 @@ CREATE TABLE IF NOT EXISTS `blacklist` ( DROP TABLE IF EXISTS `guilds`; CREATE TABLE IF NOT EXISTS `guilds` ( - `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `logging` tinyint(1) NOT NULL DEFAULT 0, - `debug` tinyint(1) NOT NULL DEFAULT 1, - `log_channel` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - PRIMARY KEY (`guild_id`) + `guild_id` varchar(255) NOT NULL, + `logging` tinyint(1) NOT NULL DEFAULT 0, + `debug` tinyint(1) NOT NULL DEFAULT 1, + `log_channel` varchar(255) DEFAULT NULL, + PRIMARY KEY (`guild_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -------------------------------------------------------- @@ -54,12 +54,12 @@ CREATE TABLE IF NOT EXISTS `guilds` ( DROP TABLE IF EXISTS `guild_settings`; CREATE TABLE IF NOT EXISTS `guild_settings` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `value` text COLLATE utf8mb4_unicode_ci NOT NULL, - PRIMARY KEY (`id`), - KEY `fk_guildsetting_guilds` (`guild_id`) + `id` int(11) NOT NULL AUTO_INCREMENT, + `guild_id` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `value` text NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_guildsetting_guilds` (`guild_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -------------------------------------------------------- @@ -70,10 +70,10 @@ CREATE TABLE IF NOT EXISTS `guild_settings` ( DROP TABLE IF EXISTS `logging`; CREATE TABLE IF NOT EXISTS `logging` ( - `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `channel_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `enabled` tinyint(1) NOT NULL DEFAULT 0, - PRIMARY KEY (`guild_id`,`channel_id`) + `guild_id` varchar(255) NOT NULL, + `channel_id` varchar(255) NOT NULL, + `enabled` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`guild_id`,`channel_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -------------------------------------------------------- @@ -84,32 +84,33 @@ CREATE TABLE IF NOT EXISTS `logging` ( DROP TABLE IF EXISTS `log_message`; CREATE TABLE IF NOT EXISTS `log_message` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `channel_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `message_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `user_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `date_sent` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), - `message` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `edited_message` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `message_id` (`message_id`), - KEY `fk_log_guilds` (`guild_id`) + `id` int(11) NOT NULL AUTO_INCREMENT, + `guild_id` varchar(255) NOT NULL, + `channel_id` varchar(255) NOT NULL, + `message_id` varchar(255) NOT NULL, + `user_id` varchar(255) NOT NULL, + `date_sent` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `message` varchar(2000) DEFAULT NULL, + `edited_message` varchar(2000) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `message_id` (`message_id`), + KEY `fk_log_guilds` (`guild_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -------------------------------------------------------- -- --- Table structure for table `permissions` +-- Table structure for table `tasks` -- -DROP TABLE IF EXISTS `permissions`; -CREATE TABLE IF NOT EXISTS `permissions` ( - `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `role_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `type` int(11) DEFAULT NULL, - `command` int(11) DEFAULT NULL, - PRIMARY KEY (`guild_id`,`role_id`) +DROP TABLE IF EXISTS `tasks`; +CREATE TABLE IF NOT EXISTS `tasks` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_id` varchar(255) NOT NULL, + `task_class` varchar(255) NOT NULL, + `payload` text DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `tasks_unique_taskid` (`task_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -------------------------------------------------------- @@ -120,13 +121,13 @@ CREATE TABLE IF NOT EXISTS `permissions` ( DROP TABLE IF EXISTS `web_users`; CREATE TABLE IF NOT EXISTS `web_users` ( - `id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `email` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, - `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `expires` timestamp NOT NULL DEFAULT current_timestamp(), - `refresh_token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - PRIMARY KEY (`id`) + `id` varchar(255) NOT NULL, + `email` mediumtext NOT NULL, + `token` varchar(255) NOT NULL, + `expires` timestamp NOT NULL DEFAULT current_timestamp(), + `refresh_token` varchar(255) NOT NULL, + `remember_token` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- @@ -137,25 +138,25 @@ CREATE TABLE IF NOT EXISTS `web_users` ( -- Constraints for table `blacklist` -- ALTER TABLE `blacklist` - ADD CONSTRAINT `fk_blacklist_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE; + ADD CONSTRAINT `fk_blacklist_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE; -- -- Constraints for table `guild_settings` -- ALTER TABLE `guild_settings` - ADD CONSTRAINT `fk_guildsetting_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE; + ADD CONSTRAINT `fk_guildsetting_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE; -- -- Constraints for table `logging` -- ALTER TABLE `logging` - ADD CONSTRAINT `fk_logging_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE; + ADD CONSTRAINT `fk_logging_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE; -- -- Constraints for table `log_message` -- ALTER TABLE `log_message` - ADD CONSTRAINT `fk_log_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE; + ADD CONSTRAINT `fk_log_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE; COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/src/main/java/me/bobert/netneutrality/NetNeutrality.java b/src/main/java/me/bobert/netneutrality/NetNeutrality.java index 3f37905..3270160 100644 --- a/src/main/java/me/bobert/netneutrality/NetNeutrality.java +++ b/src/main/java/me/bobert/netneutrality/NetNeutrality.java @@ -7,25 +7,28 @@ package me.bobert.netneutrality; */ import io.sentry.Sentry; +import me.bobert.netneutrality.core.LogHandler; +import me.bobert.netneutrality.core.command.CommandManager; +import me.bobert.netneutrality.core.db.SessionManager; +import me.bobert.netneutrality.core.tasks.TaskManager; +import me.bobert.netneutrality.core.thread.BaseThreadFactory; +import me.bobert.netneutrality.core.thread.ThreadManager; +import me.bobert.netneutrality.hooks.guilds.ServerJoinHook; +import me.bobert.netneutrality.hooks.interaction.ButtonInteractionHook; +import me.bobert.netneutrality.hooks.interaction.ModalInteractionHook; +import me.bobert.netneutrality.hooks.interaction.SlashCommandHook; +import me.bobert.netneutrality.hooks.message.EmojiReactionHook; +import me.bobert.netneutrality.hooks.message.MessageReceivedHook; +import me.bobert.netneutrality.http.server.WebApi; +import me.bobert.netneutrality.music.MusicManager; +import me.bobert.netneutrality.thread.ShutdownThread; +import me.bobert.netneutrality.utils.Settings; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.exceptions.InvalidTokenException; import net.dv8tion.jda.api.requests.GatewayIntent; -import me.bobert.netneutrality.command.CommandManager; -import me.bobert.netneutrality.core.LogHandler; -import me.bobert.netneutrality.db.DBController; -import me.bobert.netneutrality.hooks.SlashCommandHooks; -import me.bobert.netneutrality.hooks.guilds.ServerJoinHandler; -import me.bobert.netneutrality.hooks.message.EditHandler; -import me.bobert.netneutrality.hooks.message.MessageReceivedHandler; -import me.bobert.netneutrality.http.server.WebApi; -import me.bobert.netneutrality.music.MusicManager; -import me.bobert.netneutrality.thread.BaseThreadFactory; -import me.bobert.netneutrality.thread.ShutdownThread; -import me.bobert.netneutrality.thread.ThreadManager; -import me.bobert.netneutrality.utils.Settings; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,7 +37,7 @@ import org.apache.logging.log4j.core.config.Configurator; public class NetNeutrality { private static JDA jda; - public final static Logger logger = LogManager.getLogger(NetNeutrality.class); + private final static Logger logger = LogManager.getLogger(NetNeutrality.class); public static final String VERSION = Settings.getInstance().getMavenVersion(); public static void main(String[] args) { @@ -57,12 +60,15 @@ public class NetNeutrality { Configurator.setLevel("net.dv8tion.jda", Level.DEBUG); } - DBController.init(); + // DatabaseMigration.migrate(); + SessionManager.getSessionFactory(); initJDA(); - CommandManager.init(); + CommandManager.init(jda); MusicManager.initPlayerManager(); - jda.addEventListener(new MessageReceivedHandler(), new EditHandler(), new ServerJoinHandler()); - jda.addEventListener(new SlashCommandHooks()); + TaskManager.getInstance().init(); + jda.addEventListener(new ServerJoinHook()); + jda.addEventListener(new MessageReceivedHook(), new SlashCommandHook()); + jda.addEventListener(new ModalInteractionHook(), new ButtonInteractionHook(), new EmojiReactionHook()); new WebApi(Integer.parseInt(Settings.getInstance().get("NETNEUTRALITY_API_PORT", "9000"))); jda.getPresence().setActivity(Activity.watching("you")); @@ -73,7 +79,7 @@ public class NetNeutrality { public static void initJDA() { String token = Settings.getInstance().get("NETNEUTRALITY_BOT_TOKEN"); if (token == null) { - DBController.shutdown(); + SessionManager.shutdown(); throw new RuntimeException("No bot token provided, exiting."); } @@ -95,7 +101,7 @@ public class NetNeutrality { MusicManager.disconnectPlayers(); ThreadManager.shutdownExecutor(); disconnect(); - DBController.shutdown(); + SessionManager.shutdown(); System.out.println("[Net Neutrality] Shutdown complete"); } diff --git a/src/main/java/me/bobert/netneutrality/command/AbstractAdminCommand.java b/src/main/java/me/bobert/netneutrality/command/AbstractAdminCommand.java new file mode 100644 index 0000000..4f96d25 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/command/AbstractAdminCommand.java @@ -0,0 +1,26 @@ +package me.bobert.netneutrality.command; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; + +public abstract class AbstractAdminCommand extends AbstractCommand { + + @Override + public boolean needAdmin() { + return true; + } + + @Override + public DefaultMemberPermissions getSlashCommandPermission() { + return DefaultMemberPermissions.DISABLED; + } + + @Override + public CommandType getType() { + return CommandType.ADMINISTRATION; + } +} diff --git a/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java b/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java index f02d43b..771a040 100644 --- a/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java @@ -6,13 +6,14 @@ package me.bobert.netneutrality.command; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.context.MessageCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; -public abstract class AbstractCommand implements ICommand { +public abstract class AbstractCommand implements Command { @Override public boolean isSlashCommand() { @@ -25,12 +26,12 @@ public abstract class AbstractCommand implements ICommand { } @Override - public boolean canBeExecutedBy(Guild guild, Member member) { + public boolean canBeExecutedBy(Member member) { return true; } @Override - public boolean canBeExecutedFrom(Guild guild, MessageChannel channel) { + public boolean canBeExecutedFrom(MessageChannel channel) { return true; } diff --git a/src/main/java/me/bobert/netneutrality/command/AbstractMusicCommand.java b/src/main/java/me/bobert/netneutrality/command/AbstractMusicCommand.java index c59f7a3..3e235aa 100644 --- a/src/main/java/me/bobert/netneutrality/command/AbstractMusicCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/AbstractMusicCommand.java @@ -12,6 +12,7 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; public abstract class AbstractMusicCommand extends AbstractCommand { @@ -32,7 +33,12 @@ public abstract class AbstractMusicCommand extends AbstractCommand { } @Override - public boolean canBeExecutedBy(Guild guild, Member member) { + public boolean canBeExecutedBy(Member member) { + if (member == null) { + return true; + } + + Guild guild = member.getGuild(); String roleDb = GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE); if (roleDb != null && !roleDb.isEmpty()) { @@ -54,14 +60,15 @@ public abstract class AbstractMusicCommand extends AbstractCommand { } @Override - public boolean canBeExecutedFrom(Guild guild, MessageChannel channel) { - if (channel.getType() != ChannelType.TEXT) - return false; + public boolean canBeExecutedFrom(MessageChannel channel) { + if (channel instanceof GuildMessageChannel guildMessageChannel) { + if (channel.getType() != ChannelType.TEXT) + return false; - String channelDb = GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL); - - if (channelDb != null && !channelDb.isEmpty()) { - return channelDb.equals(channel.getId()); + String channelDb = GuildManager.getManager(guildMessageChannel.getGuild()).getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL); + if (channelDb != null && !channelDb.isEmpty()) { + return channelDb.equals(channel.getId()); + } } return true; diff --git a/src/main/java/me/bobert/netneutrality/command/ICommand.java b/src/main/java/me/bobert/netneutrality/command/Command.java similarity index 73% rename from src/main/java/me/bobert/netneutrality/command/ICommand.java rename to src/main/java/me/bobert/netneutrality/command/Command.java index 6d290b3..193781d 100644 --- a/src/main/java/me/bobert/netneutrality/command/ICommand.java +++ b/src/main/java/me/bobert/netneutrality/command/Command.java @@ -5,15 +5,16 @@ package me.bobert.netneutrality.command; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; -public interface ICommand { +public interface Command { boolean needAdmin(); @@ -33,9 +34,9 @@ public interface ICommand { CommandType getType(); - boolean canBeExecutedBy(Guild guild, Member member); + boolean canBeExecutedBy(Member member); - boolean canBeExecutedFrom(Guild guild, MessageChannel channel); + boolean canBeExecutedFrom(MessageChannel channel); @Deprecated CommandExecutionResult execute(MessageCommandContext context); diff --git a/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java b/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java index 9267025..3d57907 100644 --- a/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java @@ -5,29 +5,22 @@ package me.bobert.netneutrality.command.admin; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.command.CommandType; +import me.bobert.netneutrality.command.AbstractAdminCommand; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.LogHandler; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; -import me.bobert.netneutrality.db.DBController; -import me.bobert.netneutrality.db.controllers.DBBlacklist; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.db.entities.OBlacklist; -import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; +import me.bobert.netneutrality.db.repository.BlacklistRepository; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import java.util.ArrayList; +import java.util.List; -public class BlacklistCommand extends AbstractCommand { - - @Override - public boolean needAdmin() { - return true; - } +public class BlacklistCommand extends AbstractAdminCommand { @Override public boolean isEnabled() { @@ -49,11 +42,6 @@ public class BlacklistCommand extends AbstractCommand { return "Manages the blacklist of your server."; } - @Override - public DefaultMemberPermissions getSlashCommandPermission() { - return DefaultMemberPermissions.DISABLED; - } - @Override public void registerSlashCommandOptions(SlashCommandData data) { data.addSubcommands( @@ -65,11 +53,6 @@ public class BlacklistCommand extends AbstractCommand { ); } - @Override - public CommandType getType() { - return CommandType.ADMINISTRATION; - } - @Override public CommandExecutionResult execute(MessageCommandContext context) { return CommandExecutionResult.none(); @@ -78,34 +61,42 @@ public class BlacklistCommand extends AbstractCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { String sub = context.getEvent().getFullCommandName().split(" ")[1]; - DBBlacklist controller = DBController.get(DBBlacklist.class); String word = context.getEvent().getOption("word", "", OptionMapping::getAsString); - context.ackAndWait(true); + BlacklistRepository repository = new BlacklistRepository(); switch (sub) { - case "get" -> { + case "get" -> context.async("Retrieving list, please wait...", asyncMessage -> { + List blacklists = new ArrayList<>(); + try { + blacklists = repository.getBlacklistForGuild(context.getGuild()); + } catch (Exception e) { + LogHandler.log(e); + } - context.getHook().editOriginal("Retrieving list, please wait...").queue(t -> { - ArrayList blacklists = new ArrayList<>(); - try { - blacklists = controller.getBlacklistForGuild(context.getGuild()); - } catch (Exception e) { - LogHandler.log(e); - } + StringBuilder sb = new StringBuilder(); + sb.append("List of blacklisted words for this server [").append(context.getGuild().getName()).append("]: \n"); + for (OBlacklist bl : blacklists) { + sb.append(bl.getWord()).append(" -> ").append(bl.getEnabled() ? "enabled" : "disabled").append("\n"); + } - StringBuilder sb = new StringBuilder(); - for (OBlacklist bl : blacklists) { - sb.append(bl.getWord()).append(" -> ").append(bl.getEnabled() ? "enabled" : "disabled").append("\n"); - } + if (blacklists.isEmpty()) { + sb.append("No word found"); + } - t.editMessage("List of blacklisted words for this server [" + context.getGuild().getName() + "] :\n" + (blacklists.isEmpty() ? "No word found" : sb.toString())).queue(); - - }); + asyncMessage.edit(sb.toString()); + }); + case "add" -> { + return repository.add(context.getGuild(), context.getGuildUser(), word); + } + case "remove" -> { + return repository.delete(context.getGuild(), word); + } + case "disable" -> { + return repository.disable(context.getGuild(), word); + } + case "enable" -> { + return repository.enable(context.getGuild(), word); } - case "add" -> controller.add(word, context); - case "remove" -> controller.delete(word, context); - case "disable" -> controller.disable(word, context); - case "enable" -> controller.enable(word, context); } return CommandExecutionResult.none(); diff --git a/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java b/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java new file mode 100644 index 0000000..04a8862 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java @@ -0,0 +1,137 @@ +package me.bobert.netneutrality.command.admin; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.command.AbstractAdminCommand; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.tasks.TaskManager; +import me.bobert.netneutrality.tasks.emoji.EmojiReactionRoleTask; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData; + +public class ReactionCommand extends AbstractAdminCommand { + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isGuildOnly() { + return true; + } + + @Override + public String getCommandName() { + return "reaction"; + } + + @Override + public String getDescription() { + return "Manages emoji reactions on this server"; + } + + @Override + public void registerSlashCommandOptions(SlashCommandData data) { + data.addSubcommandGroups( + new SubcommandGroupData("emoji", "Give roles to users using emojis").addSubcommands( + new SubcommandData("setup", "Add a role to a message").addOptions( + new OptionData(OptionType.STRING, "messageid", "The ID of the message", true), + new OptionData(OptionType.STRING, "emoji", "Emoji associated with the role", true), + new OptionData(OptionType.ROLE, "role", "Role to add to the emoji", true) + ), + new SubcommandData("add", "Add a role to an existing message").addOptions( + new OptionData(OptionType.STRING, "messageid", "The ID of the message", true), + new OptionData(OptionType.STRING, "emoji", "Emoji associated with the role", true), + new OptionData(OptionType.ROLE, "role", "Role to add to the emoji", true) + ), + new SubcommandData("remove", "Remove a role from a message").addOptions( + new OptionData(OptionType.STRING, "messageid", "The ID of the message", true), + new OptionData(OptionType.STRING, "emoji", "Emoji to remove from the message", true) + ), + new SubcommandData("delete", "Remove all roles from a message").addOptions( + new OptionData(OptionType.STRING, "messageid", "The ID of the message", true) + ) + ) + ); + } + + @Override + public CommandExecutionResult execute(SlashCommandContext context) { + context.ack(); + + String guildId = context.getGuild().getId(); + String messageId = context.getEvent().getOption("messageid", OptionMapping::getAsString); + String sub = context.getSubCommand(); + String action = context.getEvent().getFullCommandName().split(" ")[2]; + + switch (sub) { + case "emoji" -> { + if ("setup".equals(action)) { + String emoji = context.getEvent().getOption("emoji", OptionMapping::getAsString); + String role = context.getEvent().getOption("role", OptionMapping::getAsString); + + EmojiReactionRoleTask task = new EmojiReactionRoleTask(guildId, messageId); + if (TaskManager.getInstance().exists(task.getId())) { + return CommandExecutionResult.error("The message ID is already registered, use the add subcommand instead."); + } + task.addEmoji(emoji, role); + + TaskManager.getInstance().insert(context, task); + return CommandExecutionResult.success("Added emoji to message successfully!"); + } + + if ("add".equals(action)) { + String emoji = context.getEvent().getOption("emoji", OptionMapping::getAsString); + String role = context.getEvent().getOption("role", OptionMapping::getAsString); + + String id = String.format("%s:%s", guildId, messageId); + EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); + if (task == null) { + return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); + } + + task.addEmoji(emoji, role); + TaskManager.getInstance().persistTask(task); + return CommandExecutionResult.success("Added emoji to message successfully!"); + } + + if ("remove".equals(action)) { + String emoji = context.getEvent().getOption("emoji", OptionMapping::getAsString); + + String id = String.format("%s:%s", guildId, messageId); + EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); + if (task == null) { + return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); + } + + task.removeEmoji(emoji); + TaskManager.getInstance().persistTask(task); + return CommandExecutionResult.success("Removed emoji from message successfully!"); + } + + if ("delete".equals(action)) { + String id = String.format("%s:%s", guildId, messageId); + EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); + if (task == null) { + return CommandExecutionResult.error("The message ID provided isn't registered with the bot"); + } + + TaskManager.getInstance().unpersistTask(task); + return CommandExecutionResult.success("Unregistered the message ID successfully!"); + } + } + } + + + return CommandExecutionResult.none(); + } +} diff --git a/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java b/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java index 840ab45..39ee2c3 100644 --- a/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java @@ -6,10 +6,10 @@ package me.bobert.netneutrality.command.basic; */ import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; import me.bobert.netneutrality.command.CommandType; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.utils.MessageFunction; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; @@ -57,10 +57,10 @@ public class HelpCommand extends AbstractCommand { @Override public CommandExecutionResult execute(MessageCommandContext context) { if (context.getArgs().length == 0) { - List help = MessageFunction.buildHelpMessage(context.getGuildUser()); + List help = MessageFunction.buildHelpMessage(); context.reply(new MessageCreateBuilder().addContent(context.getDiscordUser().getAsMention()).addContent(" help has been sent your way!").build()); - context.replyPrivate(help); + context.sendMessageInPrivate(help); } return CommandExecutionResult.none(); @@ -68,7 +68,7 @@ public class HelpCommand extends AbstractCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { - List help = MessageFunction.buildHelpMessage(context.getGuildUser()); + List help = MessageFunction.buildHelpMessage(); context.sendMessageInPrivate(help); return CommandExecutionResult.success("Help has been sent in DM!"); diff --git a/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java b/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java index e287444..5dc94dc 100644 --- a/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java @@ -6,10 +6,10 @@ package me.bobert.netneutrality.command.basic; */ import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; import me.bobert.netneutrality.command.CommandType; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class PingCommand extends AbstractCommand { diff --git a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java index a74d50d..22c2af5 100644 --- a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java @@ -5,18 +5,16 @@ package me.bobert.netneutrality.command.bot; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.command.CommandType; +import me.bobert.netneutrality.command.AbstractAdminCommand; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.db.GSettings; import me.bobert.netneutrality.music.MusicManager; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; -import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; @@ -27,12 +25,7 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import java.util.List; import java.util.Map; -public class ConfigureCommand extends AbstractCommand { - - @Override - public boolean needAdmin() { - return true; - } +public class ConfigureCommand extends AbstractAdminCommand { @Override public boolean isEnabled() { @@ -54,11 +47,6 @@ public class ConfigureCommand extends AbstractCommand { return "Used to configure the bot on this server"; } - @Override - public DefaultMemberPermissions getSlashCommandPermission() { - return DefaultMemberPermissions.DISABLED; - } - @Override public void registerSlashCommandOptions(SlashCommandData data) { data.addSubcommands( @@ -74,11 +62,6 @@ public class ConfigureCommand extends AbstractCommand { ); } - @Override - public CommandType getType() { - return CommandType.ADMINISTRATION; - } - @Override public CommandExecutionResult execute(MessageCommandContext context) { String[] args = context.getArgs(); @@ -213,7 +196,7 @@ public class ConfigureCommand extends AbstractCommand { public CommandExecutionResult execute(SlashCommandContext context) { String sub = context.getEvent().getFullCommandName().split(" ")[1]; GuildManager guildManager = GuildManager.getManager(context.getGuild()); - context.ackAndWait(true); + context.ephemeral(); if ("music-role".equals(sub)) { Role role = context.getEvent().getOption("role", OptionMapping::getAsRole); @@ -259,11 +242,11 @@ public class ConfigureCommand extends AbstractCommand { return CommandExecutionResult.success(String.format("Default volume for music player is now %d", volume)); } - if ("debug".equals(sub)) { + if ("error-logging".equals(sub)) { boolean debug = context.getEvent().getOption("debug", true, OptionMapping::getAsBoolean); guildManager.setDebug(debug); - return CommandExecutionResult.success(String.format("Error logging is now %s for this guild", (debug ? "enabled" : "disabled"))); + return CommandExecutionResult.success("Error logging is now %s for this guild", (debug ? "enabled" : "disabled")); } if ("show-all".equals(sub)) { @@ -284,6 +267,6 @@ public class ConfigureCommand extends AbstractCommand { context.replyEphemeral(builder.build()); } - return CommandExecutionResult.none(); + return CommandExecutionResult.error("Unknown subcommand: " + sub); } } diff --git a/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java b/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java index 597492c..8cebebe 100644 --- a/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java @@ -6,11 +6,11 @@ package me.bobert.netneutrality.command.crypto; */ import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; import me.bobert.netneutrality.command.CommandType; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.LogHandler; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.http.WebModule; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; @@ -79,7 +79,8 @@ public class CryptoPriceCommand extends AbstractCommand { String crypto = args[0].toUpperCase(); String finalCurrency = currency; - context.getChannel().sendMessage("Retreiving price, please wait...").queue(t -> { + + context.async("Retreiving price, please wait...", asyncMessage -> { String price; Locale usa = new Locale("en", "US"); NumberFormat dollarFormat = NumberFormat.getCurrencyInstance(usa); @@ -89,15 +90,15 @@ public class CryptoPriceCommand extends AbstractCommand { WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, crypto, finalCurrency.toUpperCase())).request(); JSONObject json = http.getAsObject(); if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) { - t.editMessage("Crypto doesn't exist.. yet").queue(); + asyncMessage.edit("Crypto doesn't exist.. yet"); return; } price = dollarFormat.format(json.getDouble(finalCurrency)); - t.editMessage(String.format("The current price of %s in %s is %s", crypto, finalCurrency, price)).queue(); + asyncMessage.edit("The current price of %s in %s is %s", crypto, finalCurrency, price); } catch (JSONException | IllegalArgumentException | ArithmeticException e) { - t.editMessage(String.format("Error getting price of %s!", crypto)).queue(); + asyncMessage.edit("Error getting price of %s", crypto); LogHandler.log(e); } }); @@ -122,28 +123,26 @@ public class CryptoPriceCommand extends AbstractCommand { } String finalCurrency = currency.toUpperCase(); - context.getEvent().deferReply().queue(interactionHook -> { + context.async(asyncMessage -> { try { WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, symbol, finalCurrency)).request(); JSONObject json = http.getAsObject(); if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) { - interactionHook.editOriginal("Crypto doesn't exist.. yet").queue(); + asyncMessage.edit("Crypto doesn't exist.. yet"); return; } double value = json.getDouble(finalCurrency); String price = dollarFormat.format(value); - interactionHook.editOriginal(String.format("The current price of %s in %s is %s", symbol, finalCurrency, price)).queue(); + asyncMessage.edit("The current price of %s in %s is %s", symbol, finalCurrency, price); } catch (JSONException | IllegalArgumentException | ArithmeticException e) { - interactionHook.editOriginal(String.format("Error getting price of %s!", symbol)).queue(); + asyncMessage.edit("Error getting price of %s!", symbol); LogHandler.log(e); } - - }); return CommandExecutionResult.none(); diff --git a/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java b/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java new file mode 100644 index 0000000..33ed766 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java @@ -0,0 +1,67 @@ +package me.bobert.netneutrality.command.git; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.command.AbstractCommand; +import me.bobert.netneutrality.command.CommandType; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.tasks.TaskManager; +import me.bobert.netneutrality.tasks.git.ReportIssueTask; +import me.bobert.netneutrality.utils.Settings; +import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; + +public class ReportIssueCommand extends AbstractCommand { + @Override + public boolean needAdmin() { + return false; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isGuildOnly() { + return false; + } + + @Override + public String getCommandName() { + return "report-bug"; + } + + @Override + public String getDescription() { + return "Used to report a bug with the bot."; + } + + @Override + public void registerSlashCommandOptions(SlashCommandData data) { + + } + + @Override + public CommandType getType() { + return CommandType.UTILITY; + } + + @Override + public CommandExecutionResult execute(SlashCommandContext context) { + String gitlabHost = Settings.getInstance().get("GITLAB_HOST"); + String gitlabToken = Settings.getInstance().get("GITLAB_TOKEN"); + String projectId = Settings.getInstance().get("GITLAB_PROJECT_ID"); + + if (gitlabHost == null || gitlabToken == null || projectId == null) { + return CommandExecutionResult.error("Command is not configured, failed to create issue."); + } + + TaskManager.getInstance().insert(context, new ReportIssueTask()); + + return CommandExecutionResult.none(); + } +} diff --git a/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java b/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java index 5e5947b..2315e56 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java @@ -7,10 +7,10 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; import me.bobert.netneutrality.command.CommandType; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; @@ -44,7 +44,7 @@ public class BitconnectCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(MessageCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, false, true); + player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, true); context.deleteMessage(); return CommandExecutionResult.none(); @@ -53,9 +53,9 @@ public class BitconnectCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - context.ackAndWait(); + context.ephemeral(); - player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, false, true); + player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, true); return CommandExecutionResult.success("Ready to invest?"); } } diff --git a/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java index 30f06a6..a6cd04e 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java @@ -7,9 +7,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; @@ -46,7 +46,7 @@ public class ClearQueueCommand extends AbstractMusicCommand { public CommandExecutionResult execute(MessageCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - if (!MusicUtils.checkIfConnected(player, context)) + if (!MusicUtils.checkIfConnected(context)) return CommandExecutionResult.none(); if (context.getArgs().length == 0) { @@ -88,9 +88,9 @@ public class ClearQueueCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - context.ackAndWait(); + context.ack(); - if (!MusicUtils.checkIfConnected(manager, context)) + if (!MusicUtils.checkIfConnected(context)) return CommandExecutionResult.none(); return manager.getScheduler().getQueueManager().clearQueue(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java b/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java index 9c432c0..dd7a571 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java @@ -7,9 +7,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; @@ -39,12 +39,14 @@ public class CurrentlyPlayingCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager player = MusicManager.get(context.getGuild()); + MusicManager manager = MusicManager.get(context.getGuild()); - if (MusicUtils.checkIfConnected(player, context) && player.isCurrentlyPlaying()) { - context.reply(MessageFunction.buildCurrentlyPlayingMessage(player)); - } else { - context.reply("Bot isn't playing any tracks right now"); + if (MusicUtils.checkIfConnected(context)) { + if (!manager.isCurrentlyPlaying()) { + return CommandExecutionResult.info("Bot isn't playing any tracks right now"); + } + + context.reply(MessageFunction.buildCurrentlyPlayingMessage(manager)); } return CommandExecutionResult.none(); @@ -53,9 +55,13 @@ public class CurrentlyPlayingCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - context.ackAndWait(); + context.ack(); + + if (MusicUtils.checkIfConnected(context)) { + if (!manager.isCurrentlyPlaying()) { + return CommandExecutionResult.info("Bot isn't playing any tracks right now"); + } - if (MusicUtils.checkIfConnected(manager, context) && manager.isCurrentlyPlaying()) { context.reply(MessageFunction.buildCurrentlyPlayingMessage(manager)); } diff --git a/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java b/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java index 85c4d78..8006e89 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java @@ -7,12 +7,10 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; -import me.bobert.netneutrality.exceptions.CommandExecutionException; -import me.bobert.netneutrality.music.MusicManager; -import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.utils.MusicUtils; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class JoinCommand extends AbstractMusicCommand { @@ -39,24 +37,10 @@ public class JoinCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager player = MusicManager.get(context.getGuild()); - - if (context.getGuildUser().getVoiceState() == null) { - throw new CommandExecutionException("Voice state is null, this should not happen", context, null); - } - - if (!context.getGuildUser().getVoiceState().inAudioChannel()) { - return CommandExecutionResult.warn("You are not in a voice channel"); - } - - if (player.isConnected()) { - return CommandExecutionResult.warn("Player is already in a voice channel"); - } - - VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel(); - if (vChannel != null) { - player.connect(vChannel); + if (MusicUtils.checkIfNotConnected(context)) { context.reply("Joining current voice channel"); + + MusicUtils.connect(context, true); } return CommandExecutionResult.none(); @@ -64,25 +48,10 @@ public class JoinCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { - MusicManager player = MusicManager.get(context.getGuild()); - context.ackAndWait(); - - if (context.getGuildUser().getVoiceState() == null) { - throw new CommandExecutionException("Voice state is null, this should not happen", context, null); - } - - if (!context.getGuildUser().getVoiceState().inAudioChannel()) { - return CommandExecutionResult.warn("You are not in a voice channel"); - } - - if (player.isConnected()) { - return CommandExecutionResult.warn("Player is already in a voice channel"); - } - - VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel(); - if (vChannel != null) { - player.connect(vChannel); + if (MusicUtils.checkIfNotConnected(context)) { context.reply("Joining current voice channel"); + + MusicUtils.connect(context, true); } return CommandExecutionResult.none(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java b/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java index 806d5a7..ccaa16b 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java @@ -7,9 +7,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; @@ -40,9 +40,9 @@ public class LeaveCommand extends AbstractMusicCommand { public CommandExecutionResult execute(MessageCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - if (MusicUtils.checkIfConnected(player, context)) { - player.leave(); + if (MusicUtils.checkIfConnected(context)) { context.reply("Leaving voice channel and clearing queue"); + player.leave(); } return CommandExecutionResult.none(); @@ -51,11 +51,10 @@ public class LeaveCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - context.ackAndWait(); - if (MusicUtils.checkIfConnected(player, context)) { - player.leave(); + if (MusicUtils.checkIfConnected(context)) { context.reply("Leaving voice channel and clearing queue"); + player.leave(); } return CommandExecutionResult.none(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java b/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java index 5f3da37..be480cd 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java @@ -7,9 +7,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; @@ -39,12 +39,13 @@ public class PlayCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager player = MusicManager.get(context.getGuild()); + MusicManager manager = MusicManager.get(context.getGuild()); - if (context.getArgs().length > 0) { - player.play(context.getMessageText(), context, false, false); + if (context.getArgs().length == 0) { + return CommandExecutionResult.warn("Please specify a query"); } + manager.play(context.getMessageText(), context); return CommandExecutionResult.none(); } @@ -52,9 +53,9 @@ public class PlayCommand extends AbstractMusicCommand { public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); String query = context.getEvent().getOption("query", OptionMapping::getAsString); - context.ackAndWait(); + context.ack(); - manager.play(query, context, false, false); + manager.play(query, context); return CommandExecutionResult.none(); } } diff --git a/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java index 1d7075f..7e6a097 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java @@ -6,9 +6,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; @@ -44,6 +44,7 @@ public class QueueCommand extends AbstractMusicCommand { @Override public void registerSlashCommandOptions(SlashCommandData data) { + data.addSubcommands( new SubcommandData("clear-all", "Clear all queued tracks"), new SubcommandData("delete-track", "Remove a single track from the queue") @@ -62,7 +63,7 @@ public class QueueCommand extends AbstractMusicCommand { public CommandExecutionResult execute(MessageCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - if (!MusicUtils.checkIfConnected(player, context)) + if (!MusicUtils.checkIfConnected(context)) return CommandExecutionResult.none(); if (context.getArgs().length == 0) { @@ -104,54 +105,57 @@ public class QueueCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - context.ackAndWait(); + context.ack(); String sub = context.getEvent().getFullCommandName().split(" ")[1]; - if (!MusicUtils.checkIfConnected(manager, context)) { - return CommandExecutionResult.none(); - } + if (MusicUtils.checkIfConnected(context)) { - switch (sub) { - case "clear-all" -> { - return manager.getScheduler().getQueueManager().clearQueue(); - } - - case "delete-track" -> { - int position = context.getEvent().getOption("position", 0, OptionMapping::getAsInt); - return manager.getScheduler().getQueueManager().removeFromQueue(position); - } - - case "show-queue" -> context.reply(MessageFunction.buildViewQueueMessage(manager, true)); - - case "shuffle" -> { - return manager.getScheduler().getQueueManager().shuffle(); - } - - case "from-user" -> { - Member user = context.getEvent().getOption("user", OptionMapping::getAsMember); - if (user == null) { - context.reply("The user isn't from this guild"); - break; + switch (sub) { + case "clear-all" -> { + return manager.getScheduler().getQueueManager().clearQueue(); } - context.reply(MessageFunction.buildUserTracksMessage(manager, user)); - } - - case "remove-from-user" -> { - Member member = context.getEvent().getOption("user", OptionMapping::getAsMember); - if (member == null) { - context.reply("The user isn't from this guild"); - break; + case "delete-track" -> { + int position = context.getEvent().getOption("position", 0, OptionMapping::getAsInt); + return manager.getScheduler().getQueueManager().removeFromQueue(position); } - int size = manager.getScheduler() - .getQueueManager() - .getQueueWithFilter(trackInfo -> member.equals(trackInfo.getRequester())) - .size(); + case "show-queue" -> { + if (manager.getQueueSize() == 0) { + return CommandExecutionResult.info("Queue is empty!"); + } + + context.reply(MessageFunction.buildViewQueueMessage(manager, true)); + } + + case "shuffle" -> { + return manager.getScheduler().getQueueManager().shuffle(); + } + + case "from-user" -> { + Member user = context.getEvent().getOption("user", OptionMapping::getAsMember); + if (user == null) { + return CommandExecutionResult.warn("The user isn't from this guild"); + } + + context.reply(MessageFunction.buildUserTracksMessage(manager, user)); + } + + case "remove-from-user" -> { + Member member = context.getEvent().getOption("user", OptionMapping::getAsMember); + if (member == null) { + return CommandExecutionResult.warn("The user isn't from this guild"); + } + + int size = manager.getScheduler() + .getQueueManager() + .getQueueWithFilter(trackInfo -> member.equals(trackInfo.getRequester())) + .size(); + + if (size == 0) { + return CommandExecutionResult.info(String.format("No tracks added by user %s", member.getEffectiveName())); + } - if (size == 0) { - context.reply("No tracks added by user %s", member.getEffectiveName()); - } else { manager.getScheduler().getQueueManager().removeTracksFromUser(member); context.reply("Removed all tracks from user %s", member.getEffectiveName()); } diff --git a/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java index e4ea2c2..fbc08a3 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java @@ -7,8 +7,8 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; @@ -37,9 +37,9 @@ public class ShuffleCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - context.ackAndWait(); + context.ack(); - if (MusicUtils.checkIfConnected(manager, context)) { + if (MusicUtils.checkIfConnected(context)) { return manager.getScheduler().getQueueManager().shuffle(); } diff --git a/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java b/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java index 7db97f4..9a927c6 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java @@ -7,9 +7,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import me.bobert.netneutrality.utils.UserFunction; @@ -45,7 +45,7 @@ public class SkipCommand extends AbstractMusicCommand { public CommandExecutionResult execute(MessageCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - if (!MusicUtils.checkIfConnected(player, context)) + if (!MusicUtils.checkIfConnected(context)) return CommandExecutionResult.none(); if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) { @@ -73,9 +73,9 @@ public class SkipCommand extends AbstractMusicCommand { public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); int skips = context.getEvent().getOption("skip", 1, OptionMapping::getAsInt); - context.ackAndWait(); + context.ack(); - if (MusicUtils.checkIfConnected(manager, context)) { + if (MusicUtils.checkIfConnected(context)) { return manager.getScheduler().getQueueManager().skip(skips); } diff --git a/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java b/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java index 48e772d..70b9182 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java @@ -7,9 +7,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import me.bobert.netneutrality.utils.UserFunction; @@ -41,7 +41,7 @@ public class StopCommand extends AbstractMusicCommand { public CommandExecutionResult execute(MessageCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - if (!MusicUtils.checkIfConnected(player, context)) + if (!MusicUtils.checkIfConnected(context)) return CommandExecutionResult.none(); // Need a resume command before @@ -56,9 +56,9 @@ public class StopCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - context.ackAndWait(); + context.ack(); - if (MusicUtils.checkIfConnected(manager, context)) { + if (MusicUtils.checkIfConnected(context)) { manager.stopTrack(); return CommandExecutionResult.success("Stopping music playback"); } diff --git a/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java index 84808dc..ec8031a 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java @@ -6,9 +6,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; @@ -40,7 +40,7 @@ public class ViewQueueCommand extends AbstractMusicCommand { public CommandExecutionResult execute(MessageCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - if (MusicUtils.checkIfConnected(player, context)) { + if (MusicUtils.checkIfConnected(context)) { int queueSize = player.getQueueSize(); if (queueSize > 0) { context.reply(MessageFunction.buildViewQueueMessage(player, true)); @@ -55,16 +55,15 @@ public class ViewQueueCommand extends AbstractMusicCommand { @Override public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - context.ackAndWait(); + context.ack(); - if (MusicUtils.checkIfConnected(manager, context)) { + if (MusicUtils.checkIfConnected(context)) { int queueSize = manager.getQueueSize(); - - if (queueSize > 0) { - context.reply(MessageFunction.buildViewQueueMessage(manager, true)); - } else { + if (queueSize == 0) { return CommandExecutionResult.warn("Queue is empty!"); } + + context.reply(MessageFunction.buildViewQueueMessage(manager, true)); } return CommandExecutionResult.none(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java b/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java index 1a7178c..d5906c6 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java @@ -7,9 +7,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import me.bobert.netneutrality.utils.UserFunction; @@ -45,7 +45,7 @@ public class VolumeCommand extends AbstractMusicCommand { public CommandExecutionResult execute(MessageCommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - if (!MusicUtils.checkIfConnected(player, context)) + if (!MusicUtils.checkIfConnected(context)) return CommandExecutionResult.none(); if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) { @@ -71,7 +71,7 @@ public class VolumeCommand extends AbstractMusicCommand { MusicManager manager = MusicManager.get(context.getGuild()); int volume = context.getEvent().getOption("volume", 30, OptionMapping::getAsInt); - if (MusicUtils.checkIfConnected(manager, context)) { + if (MusicUtils.checkIfConnected(context)) { manager.setVolume(volume); return CommandExecutionResult.success(String.format("Volume set to %s", volume)); } diff --git a/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java b/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java index ce84c07..bcce618 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java @@ -6,9 +6,9 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; @@ -39,8 +39,8 @@ public class VoteSkipCommand extends AbstractMusicCommand { public CommandExecutionResult execute(MessageCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - if (MusicUtils.checkIfConnected(manager, context)) { - manager.voteSkip(context); + if (MusicUtils.checkIfConnected(context)) { + return manager.voteSkip(context); } return CommandExecutionResult.none(); @@ -50,7 +50,7 @@ public class VoteSkipCommand extends AbstractMusicCommand { public CommandExecutionResult execute(SlashCommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - if (MusicUtils.checkIfConnected(manager, context)) { + if (MusicUtils.checkIfConnected(context)) { return manager.voteSkip(context); } diff --git a/src/main/java/me/bobert/netneutrality/context/MessageCommandContext.java b/src/main/java/me/bobert/netneutrality/context/MessageCommandContext.java new file mode 100644 index 0000000..34d44f4 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/context/MessageCommandContext.java @@ -0,0 +1,141 @@ +package me.bobert.netneutrality.context; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.AsyncMessage; +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.utils.Settings; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class MessageCommandContext extends CommandContext { + private final MessageReceivedEvent event; + private final Message message; + + public MessageCommandContext(MessageReceivedEvent event) { + super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getAuthor()); + this.event = event; + this.message = event.getMessage(); + } + + @Override + public MessageReceivedEvent getEvent() { + return event; + } + + public String getArgument(int index) { + return getArgs()[index]; + } + + public String[] getArgs() { + String msg = message.getContentDisplay().trim(); + msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim(); + String cmd = msg.split(" ", 2)[0]; + String[] args = msg.replaceFirst("(" + cmd + ")(\\s+)?", "").split(" "); + + if (args.length == 1 && args[0].isEmpty()) + args = new String[0]; + + return args; + } + + public String getMessageText() { + String msg = message.getContentDisplay().trim(); + msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim(); + String cmd = msg.split(" ", 2)[0]; + + return msg.replaceFirst("(" + cmd + ")( )?", ""); + } + + public Message getMessage() { + return message; + } + + @Override + public boolean isAcknowledged() { + return true; + } + + @Override + public InteractionHook getInteractionHook() { + return null; + } + + @Override + public void defer(Consumer consumer) { + reply("Bot is thinking...", consumer); + } + + @Override + public void reply(MessageCreateData messageCreateData, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (getChannel() != null && getChannel().canTalk()) { + message.reply(messageCreateData).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1))); + } + } + + @Override + public void reply(Modal modal) { + + } + + @Override + public void replyEphemeral(MessageCreateData message) { + sendMessageInPrivate(message); + } + + @Override + public void ack(boolean ephemeral) { + + } + + @Override + public void deleteMessage() { + if (message != null) { + message.delete().queueAfter(1, TimeUnit.SECONDS); + } + } + + public boolean isMessageFromOwner() { + return getGuildUser().isOwner(); + } + + public boolean isMessageFromCreator() { + String creatorId = Settings.getInstance().get("NETNEUTRALITY_BOT_OWNER"); + + return creatorId.equals(getDiscordUser().getId()); + } + + @Override + public String parseCommandName() { + String rawMessage = message.getContentDisplay().trim(); + String prefix = Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$"); + + if (rawMessage.startsWith(prefix)) { + rawMessage = rawMessage.replaceFirst(String.format("^[%s\\s]{1,}", prefix), "").trim(); + // rawMessage = rawMessage.substring(prefix.length()).trim(); + + return rawMessage.split(" ", 2)[0]; + } + + return null; + } + + @Override + public String getSubCommand() { + if (getArgs().length > 0) { + return getArgument(0); + } + + return ""; + } +} diff --git a/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java b/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java new file mode 100644 index 0000000..e966127 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java @@ -0,0 +1,66 @@ +package me.bobert.netneutrality.context; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.AsyncMessage; +import me.bobert.netneutrality.core.context.AbstractContext; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; +import net.dv8tion.jda.api.events.Event; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.function.Consumer; + +public class MusicChannelContext extends AbstractContext { + + public MusicChannelContext(GuildMessageChannel channel) { + super(channel.getJDA(), channel.getGuild(), channel, null, null); + } + + @Override + public Event getEvent() { + return null; + } + + @Override + public InteractionHook getInteractionHook() { + return null; + } + + @Override + public boolean isAcknowledged() { + return true; + } + + @Override + public void deleteMessage() { + + } + + @Override + public void reply(Modal modal) { + + } + + @Override + public void reply(MessageCreateData message, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (getChannel().canTalk()) { + getChannel().sendMessage(message).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1))); + } + } + + @Override + public void defer(Consumer consumer) { + reply("Bot is thinking", consumer); + } + + @Override + public void ack(boolean ephemeral) { + + } +} diff --git a/src/main/java/me/bobert/netneutrality/context/SlashCommandContext.java b/src/main/java/me/bobert/netneutrality/context/SlashCommandContext.java new file mode 100644 index 0000000..dccd5ba --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/context/SlashCommandContext.java @@ -0,0 +1,88 @@ +package me.bobert.netneutrality.context; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.AsyncMessage; +import me.bobert.netneutrality.core.context.CommandContext; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.function.Consumer; + +public class SlashCommandContext extends CommandContext { + private final SlashCommandInteractionEvent event; + + public SlashCommandContext(SlashCommandInteractionEvent event) { + super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getUser()); + this.event = event; + } + + @Override + public SlashCommandInteractionEvent getEvent() { + return event; + } + + @Override + public boolean isAcknowledged() { + return event.isAcknowledged(); + } + + @Override + public InteractionHook getInteractionHook() { + if (!event.isAcknowledged()) + throw new RuntimeException("Interaction isn't acknowledged yet"); + + return event.getHook(); + } + + @Override + public void deleteMessage() { + // not implemented + } + + @Override + public void defer(Consumer consumer) { + event.deferReply().queue(hook -> consumer.accept(new AsyncMessage(hook))); + } + + @Override + public void reply(MessageCreateData message, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (isAcknowledged()) { + getInteractionHook().sendMessage(message).setEphemeral(ephemeral).setComponents(components) + .queue(message1 -> consumer.accept(new AsyncMessage(message1))); + return; + } + + event.reply(message).setEphemeral(ephemeral).setComponents(components) + .queue(message1 -> consumer.accept(new AsyncMessage(message1))); + } + + @Override + public void reply(Modal modal) { + event.replyModal(modal).queue(); + } + + @Override + public void ack(boolean ephemeral) { + if (event.isAcknowledged()) + throw new RuntimeException("Interaction is already acknowledged, please use getInteractionHook() instead."); + + event.deferReply(ephemeral).queue(); + } + + @Override + public String parseCommandName() { + return event.getName(); + } + + @Override + public String getSubCommand() { + return event.getFullCommandName().split(" ")[1]; + } +} diff --git a/src/main/java/me/bobert/netneutrality/context/tasks/ButtonTaskContext.java b/src/main/java/me/bobert/netneutrality/context/tasks/ButtonTaskContext.java new file mode 100644 index 0000000..70e8a4b --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/context/tasks/ButtonTaskContext.java @@ -0,0 +1,70 @@ +package me.bobert.netneutrality.context.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.AsyncMessage; +import me.bobert.netneutrality.core.context.TaskContext; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.function.Consumer; + +public class ButtonTaskContext extends TaskContext { + private final ButtonInteractionEvent event; + + public ButtonTaskContext(ButtonInteractionEvent event) { + super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getUser()); + this.event = event; + } + + @Override + public ButtonInteractionEvent getEvent() { + return event; + } + + @Override + public String getId() { + return event.getComponentId(); + } + + @Override + public InteractionHook getInteractionHook() { + return event.getHook(); + } + + @Override + public void defer(Consumer consumer) { + event.deferReply().queue(hook -> consumer.accept(new AsyncMessage(hook))); + } + + @Override + public boolean isAcknowledged() { + return event.isAcknowledged(); + } + + @Override + public void reply(MessageCreateData message, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (isAcknowledged()) { + getInteractionHook().sendMessage(message) + .setEphemeral(ephemeral) + .setComponents(components) + .queue(message1 -> consumer.accept(new AsyncMessage(message1))); + return; + } + + event.reply(message) + .setEphemeral(ephemeral) + .setComponents(components) + .queue(message1 -> consumer.accept(new AsyncMessage(message1))); + } + + @Override + public void ack(boolean ephemeral) { + event.deferReply(ephemeral).queue(); + } +} diff --git a/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java b/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java new file mode 100644 index 0000000..802a977 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java @@ -0,0 +1,78 @@ +package me.bobert.netneutrality.context.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.AsyncMessage; +import me.bobert.netneutrality.core.context.TaskContext; +import net.dv8tion.jda.api.entities.MessageReaction; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.function.Consumer; + +public class EmojiTaskContext extends TaskContext { + private final MessageReactionAddEvent event; + private final EmojiUnion emoji; + + public EmojiTaskContext(MessageReactionAddEvent event) { + super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getUser()); + this.event = event; + this.emoji = event.getEmoji(); + } + + public String getEmoji() { + return emoji.getAsReactionCode(); + } + + public MessageReaction getReaction() { + return event.getReaction(); + } + + @Override + public MessageReactionAddEvent getEvent() { + return event; + } + + @Override + public String getId() { + return String.format("%s:%s", getGuild().getId(), event.getMessageId()); + } + + @Override + public InteractionHook getInteractionHook() { + return null; + } + + @Override + public void defer(Consumer consumer) { + reply("Bot is thinking...", consumer); + } + + @Override + public boolean isAcknowledged() { + return true; + } + + @Override + public void reply(MessageCreateData message, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (getChannel() != null && getChannel().canTalk()) { + getChannel().sendMessage(message).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1))); + } + } + + @Override + public void replyEphemeral(MessageCreateData message) { + sendMessageInPrivate(message); + } + + @Override + public void ack(boolean ephemeral) { + + } +} diff --git a/src/main/java/me/bobert/netneutrality/context/tasks/ModalTaskContext.java b/src/main/java/me/bobert/netneutrality/context/tasks/ModalTaskContext.java new file mode 100644 index 0000000..9eea787 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/context/tasks/ModalTaskContext.java @@ -0,0 +1,66 @@ +package me.bobert.netneutrality.context.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.AsyncMessage; +import me.bobert.netneutrality.core.context.TaskContext; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.function.Consumer; + +public class ModalTaskContext extends TaskContext { + private final ModalInteractionEvent event; + + public ModalTaskContext(ModalInteractionEvent event) { + super(event.getJDA(), event.getGuild(), event.getChannel(), event.getMember(), event.getUser()); + this.event = event; + } + + @Override + public ModalInteractionEvent getEvent() { + return event; + } + + @Override + public String getId() { + return event.getModalId(); + } + + @Override + public InteractionHook getInteractionHook() { + return event.getHook(); + } + + @Override + public void defer(Consumer consumer) { + event.deferReply().queue(hook -> consumer.accept(new AsyncMessage(hook))); + } + + @Override + public boolean isAcknowledged() { + return event.isAcknowledged(); + } + + @Override + public void reply(MessageCreateData message, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (isAcknowledged()) { + getInteractionHook().sendMessage(message).setEphemeral(ephemeral).setComponents(components) + .queue(message1 -> consumer.accept(new AsyncMessage(message1))); + return; + } + + event.reply(message).setEphemeral(ephemeral).setComponents(components) + .queue(message1 -> consumer.accept(new AsyncMessage(message1))); + } + + @Override + public void ack(boolean ephemeral) { + event.deferReply(ephemeral).queue(); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/AsyncMessage.java b/src/main/java/me/bobert/netneutrality/core/AsyncMessage.java new file mode 100644 index 0000000..048fa31 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/AsyncMessage.java @@ -0,0 +1,97 @@ +package me.bobert.netneutrality.core; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.AbstractContext; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.Event; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.dv8tion.jda.api.utils.messages.MessageEditData; + +import java.util.function.Consumer; + +public class AsyncMessage extends AbstractContext { + private final InteractionHook hook; + private final Message message; + + public AsyncMessage(InteractionHook hook) { + super(hook.getJDA(), hook.getInteraction().getGuild(), hook.getInteraction().getMessageChannel(), hook.getInteraction().getMember(), hook.getInteraction().getUser()); + this.hook = hook; + this.message = null; + } + + public AsyncMessage(Message message) { + super(message.getJDA(), message.getGuild(), message.getChannel(), message.getMember(), message.getAuthor()); + this.hook = null; + this.message = message; + } + + @Override + public Event getEvent() { + return null; + } + + @Override + public InteractionHook getInteractionHook() { + return hook; + } + + @Override + public boolean isAcknowledged() { + return true; + } + + @Override + public void deleteMessage() { + if (hook != null) { + hook.deleteOriginal().queue(); + } + + if (message != null) { + message.delete().queue(); + } + } + + @Override + public void reply(Modal modal) { + + } + + @Override + public void reply(MessageCreateData messageCreateData, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (hook != null) { + hook.sendMessage(messageCreateData).queue(); + } + + if (message != null) { + message.reply(messageCreateData).queue(); + } + } + + @Override + public void defer(Consumer consumer) { + + } + + @Override + public void ack(boolean ephemeral) { + + } + + @Override + public void edit(MessageEditData messageEditData, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (hook != null) { + hook.editOriginal(messageEditData).queue(); + } + + if (message != null) { + message.editMessage(messageEditData).queue(); + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/BotState.java b/src/main/java/me/bobert/netneutrality/core/BotState.java index db78b80..0422632 100644 --- a/src/main/java/me/bobert/netneutrality/core/BotState.java +++ b/src/main/java/me/bobert/netneutrality/core/BotState.java @@ -34,12 +34,12 @@ public class BotState { JSONObject music = new JSONObject(); guild.put("id", manager.getGuild().getId()); - MessageChannel currentChannel = trackScheduler.getTextChannel(); - if (currentChannel == null) { - music.put("current_channel", "null"); - } else { - music.put("current_channel", currentChannel.getId()); - } +// MessageChannel currentChannel = trackScheduler.getTextChannel(); +// if (currentChannel == null) { +// music.put("current_channel", "null"); +// } else { +// music.put("current_channel", currentChannel.getId()); +// } guild.put("music", music); diff --git a/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java b/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java index 6e30312..22efa10 100644 --- a/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java +++ b/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java @@ -6,6 +6,7 @@ package me.bobert.netneutrality.core; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.core.db.SessionManager; import me.bobert.netneutrality.db.GSettings; import me.bobert.netneutrality.db.entities.OGuild; import me.bobert.netneutrality.db.entities.OGuildSetting; @@ -48,7 +49,6 @@ public class GuildConfigManager { return value; } - public void setSettingValue(GSettings setting, String value) { Session session = SessionManager.getSessionFactory().openSession(); JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId()); diff --git a/src/main/java/me/bobert/netneutrality/core/GuildManager.java b/src/main/java/me/bobert/netneutrality/core/GuildManager.java index 8ed2587..f575962 100644 --- a/src/main/java/me/bobert/netneutrality/core/GuildManager.java +++ b/src/main/java/me/bobert/netneutrality/core/GuildManager.java @@ -6,16 +6,13 @@ package me.bobert.netneutrality.core; */ import me.bobert.netneutrality.NetNeutrality; -import me.bobert.netneutrality.db.DBController; -import me.bobert.netneutrality.db.controllers.DBMessage; +import me.bobert.netneutrality.core.db.SessionManager; import me.bobert.netneutrality.db.entities.OBlacklist; import me.bobert.netneutrality.db.entities.OGuild; -import me.bobert.netneutrality.thread.ThreadManager; import me.bobert.netneutrality.utils.JpaUtils; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.entities.Role; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hibernate.Session; @@ -36,15 +33,6 @@ public class GuildManager { private final Guild guild; private OGuild guildEntity; - private GuildManager(Guild guild) { - this.guild = guild; - this.guildEntity = loadGuildFromDb(guild.getId()); - this.guildConfigManager = new GuildConfigManager(guild, this.guildEntity); - - managers.put(guild.getId(), this); - reloadBlacklistCache(); - } - private GuildManager(String guildId) { Guild temp = NetNeutrality.getJDA().getGuildById(guildId); if (temp == null) @@ -59,11 +47,7 @@ public class GuildManager { } public static GuildManager getManager(Guild guild) { - if (managers.containsKey(guild.getId())) { - return managers.get(guild.getId()); - } else { - return new GuildManager(guild); - } + return getManager(guild.getId()); } public static GuildManager getManager(String guildId) { @@ -104,6 +88,14 @@ public class GuildManager { return guildEntity.getDebug(); } + public void addRolesToMember(Member member, Role role) { + Member self = guild.getSelfMember(); + if (!self.canInteract(role)) { + logger.error("Missing permissions to interact with role {} in guild {}", role.getId(), guild.getId()); + } + guild.addRoleToMember(member, role).queue(); + } + public void setDebug(boolean toggle) { if (toggle == isDebugEnabled()) return; @@ -155,12 +147,12 @@ public class GuildManager { } public void removeFromDB() { - Session session = sessionFactory.openSession(); - session.beginTransaction(); + try(Session session = sessionFactory.openSession()) { + session.beginTransaction(); - session.remove(guildEntity); - session.getTransaction().commit(); - session.close(); + session.remove(guildEntity); + session.getTransaction().commit(); + } managers.remove(guild.getId()); } diff --git a/src/main/java/me/bobert/netneutrality/core/LogHandler.java b/src/main/java/me/bobert/netneutrality/core/LogHandler.java index 9e9a09b..02d86db 100644 --- a/src/main/java/me/bobert/netneutrality/core/LogHandler.java +++ b/src/main/java/me/bobert/netneutrality/core/LogHandler.java @@ -7,9 +7,9 @@ package me.bobert.netneutrality.core; import io.sentry.Sentry; import me.bobert.netneutrality.core.context.CommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; -import me.bobert.netneutrality.exceptions.CommandException; -import me.bobert.netneutrality.exceptions.CommandExecutionException; +import me.bobert.netneutrality.core.context.Context; +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.exceptions.ContextException; import me.bobert.netneutrality.utils.MessageFunction; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; @@ -18,7 +18,7 @@ import org.apache.logging.log4j.Logger; public class LogHandler { - private final static Logger logger = LogManager.getLogger(LogHandler.class); + private static final Logger logger = LogManager.getLogger(LogHandler.class); /** * Log an exception accordingly @@ -28,12 +28,10 @@ public class LogHandler { public static void log(Exception e) { Sentry.captureException(e); - if (e instanceof CommandExecutionException exception) { - CommandContext context = exception.getCommandContext(); - - logInternally(exception, context); - tryLogToDebugChannel(exception, context); - tryGracefulSlashContextError(exception, context); + if (e instanceof ContextException exception) { + logInternally(exception); + tryLogToDebugChannel(exception); + tryGracefulContextError(exception); return; } @@ -60,26 +58,35 @@ public class LogHandler { } } - private static void logInternally(CommandException exception, CommandContext context) { - logger.error("Exception thrown from command [{}] in guild [{}]: {}", context.getCommand().getCommandName(), context.getGuild().getId(), exception.getMessage(), exception); + private static void logInternally(ContextException exception) { + Context context = exception.getContext(); + logger.error("Exception thrown from context in guild {}: {}", context.getGuild() == null ? "PrivateMessage": context.getGuild().getId(), exception.getMessage()); + + if (context instanceof CommandContext commandContext) { + logger.error("Caused by command: {}", commandContext.getCommand().getCommandName(), exception); + } + + if (context instanceof TaskContext taskContext) { + logger.error("Caused by task with ID: {}", taskContext.getId(), exception); + } } - private static void tryLogToDebugChannel(CommandException exception, CommandContext context) { + private static void tryLogToDebugChannel(ContextException exception) { + Context context = exception.getContext(); + if (context.getGuild() != null) { - if (GuildManager.getManager(context.getGuild()).isDebugEnabled()) { + if (context.getGuildManager().isDebugEnabled()) { context.getGuild() .getTextChannels() .stream() .filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log")) .filter(TextChannel::canTalk) - .forEach(textChannel -> textChannel.sendMessageEmbeds(MessageFunction.buildDebugMessage(context, exception)).queue()); + .forEach(textChannel -> textChannel.sendMessageEmbeds(MessageFunction.buildDebugMessage(exception)).queue()); } } } - private static void tryGracefulSlashContextError(CommandException exception, CommandContext context) { - if (context instanceof SlashCommandContext slashCommandContext) { - slashCommandContext.reply(MessageFunction.buildSentryErrorMessage(exception)); - } + private static void tryGracefulContextError(ContextException exception) { + exception.getContext().reply(MessageFunction.buildSentryErrorMessage(exception)); } } diff --git a/src/main/java/me/bobert/netneutrality/command/CommandExecutionResult.java b/src/main/java/me/bobert/netneutrality/core/command/CommandExecutionResult.java similarity index 89% rename from src/main/java/me/bobert/netneutrality/command/CommandExecutionResult.java rename to src/main/java/me/bobert/netneutrality/core/command/CommandExecutionResult.java index 45a2f4a..d2206ee 100644 --- a/src/main/java/me/bobert/netneutrality/command/CommandExecutionResult.java +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandExecutionResult.java @@ -1,4 +1,4 @@ -package me.bobert.netneutrality.command; +package me.bobert.netneutrality.core.command; /* * Copyright (c) anthorob * @@ -41,6 +41,10 @@ public class CommandExecutionResult { return new CommandExecutionResult(Result.SUCCESS, message); } + public static CommandExecutionResult success(String format, Object... args) { + return success(String.format(format, args)); + } + public static CommandExecutionResult info(String message) { return new CommandExecutionResult(Result.INFO, message); } diff --git a/src/main/java/me/bobert/netneutrality/command/CommandManager.java b/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java similarity index 54% rename from src/main/java/me/bobert/netneutrality/command/CommandManager.java rename to src/main/java/me/bobert/netneutrality/core/command/CommandManager.java index c2b7e51..237dc42 100644 --- a/src/main/java/me/bobert/netneutrality/command/CommandManager.java +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java @@ -1,16 +1,18 @@ -package me.bobert.netneutrality.command; +package me.bobert.netneutrality.core.command; /* * Copyright (c) anthorob * * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.NetNeutrality; +import me.bobert.netneutrality.command.Command; import me.bobert.netneutrality.core.LogHandler; +import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.reflections.Reflections; @@ -24,35 +26,33 @@ import java.util.Set; public class CommandManager { - private final static Logger logger = LogManager.getLogger(CommandManager.class); - public static final HashMap commands = new HashMap<>(); + private static final Logger logger = LogManager.getLogger(CommandManager.class); + public static final HashMap commands = new HashMap<>(); + + public static void init(JDA jda) { + logger.info("Loading commands.."); - public static void init() { initCommands(); - registerSlashCommands(); + registerSlashCommands(jda); + + logger.info("Loaded {} commands!", commands.size()); } - public static boolean isValidCommand(String commandString) { - return commands.containsKey(commandString); - } - - public static AbstractCommand getCommand(String commandString) { + public static Command getCommand(String commandString) { return commands.get(commandString); } private static void initCommands() { - logger.info("Loading commands.."); - Reflections reflections = new Reflections("me.bobert.netneutrality.command"); - Set> classes = reflections.getSubTypesOf(AbstractCommand.class); + Set> classes = reflections.getSubTypesOf(Command.class); - for (Class s : classes) { + for (Class s : classes) { try { if (Modifier.isAbstract(s.getModifiers())) { continue; } - AbstractCommand command = s.getConstructor().newInstance(); + Command command = s.getConstructor().newInstance(); if (!command.isEnabled()) continue; @@ -65,25 +65,34 @@ public class CommandManager { LogHandler.log(e); } } - - logger.info("Loaded {} commands!", commands.size()); } - private static void registerSlashCommands() { - CommandListUpdateAction commandListUpdateAction = NetNeutrality.getJDA().updateCommands(); + private static void registerSlashCommands(JDA jda) { + CommandListUpdateAction commandListUpdateAction = jda.updateCommands(); List slashCommands = new ArrayList<>(); - for (AbstractCommand command : commands.values()) { + for (Command command : commands.values()) { if (!command.isEnabled() || !command.isSlashCommand()) { continue; } - SlashCommandData data = Commands.slash(command.getCommandName(), command.getDescription()); - data.setDefaultPermissions(command.getSlashCommandPermission()); - data.setGuildOnly(command.isGuildOnly()); - command.registerSlashCommandOptions(data); + logger.debug("Registering slash command: {}", command.getCommandName()); + String description = command.getDescription(); + if (StringUtils.isBlank(description)) { + description = "No description provided for this command"; + } + + try { + SlashCommandData data = Commands.slash(command.getCommandName(), description); + data.setDefaultPermissions(command.getSlashCommandPermission()); + data.setGuildOnly(command.isGuildOnly()); + command.registerSlashCommandOptions(data); + + slashCommands.add(data); + } catch (Exception e) { + logger.error("Error registering slash command: {}", command.getCommandName(), e); + } - slashCommands.add(data); } commandListUpdateAction.addCommands(slashCommands).queue(); diff --git a/src/main/java/me/bobert/netneutrality/core/CommandProcessor.java b/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java similarity index 64% rename from src/main/java/me/bobert/netneutrality/core/CommandProcessor.java rename to src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java index e76021a..364ce4b 100644 --- a/src/main/java/me/bobert/netneutrality/core/CommandProcessor.java +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java @@ -1,4 +1,4 @@ -package me.bobert.netneutrality.core; +package me.bobert.netneutrality.core.command; /* * Copyright (c) anthorob * @@ -6,54 +6,51 @@ package me.bobert.netneutrality.core; */ import io.sentry.Sentry; -import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.command.CommandExecutionResult; -import me.bobert.netneutrality.command.CommandManager; +import me.bobert.netneutrality.command.Command; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.LogHandler; import me.bobert.netneutrality.core.context.CommandContext; -import me.bobert.netneutrality.core.context.MessageCommandContext; -import me.bobert.netneutrality.core.context.SlashCommandContext; import me.bobert.netneutrality.exceptions.CommandExecutionException; import me.bobert.netneutrality.utils.UserFunction; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import static me.bobert.netneutrality.command.CommandExecutionResult.Result.ERROR; -import static me.bobert.netneutrality.command.CommandExecutionResult.Result.NOACTION; +import static me.bobert.netneutrality.core.command.CommandExecutionResult.Result.ERROR; +import static me.bobert.netneutrality.core.command.CommandExecutionResult.Result.NOACTION; public class CommandProcessor { - private final static Logger logger = LogManager.getLogger(CommandProcessor.class); - private final String commandName; + private static final Logger logger = LogManager.getLogger(CommandProcessor.class); private final CommandContext context; - public CommandProcessor(String commandName, CommandContext context) { - this.commandName = commandName; + public CommandProcessor(CommandContext context) { this.context = context; } public void dispatch() { + Command command = CommandManager.getCommand(context.parseCommandName()); + context.setCommand(command); - if (!CommandManager.isValidCommand(commandName)) { - return; + if (command == null) { + throw new IllegalArgumentException("Context has no command"); } - AbstractCommand command = CommandManager.getCommand(commandName); - logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild().getId()); + logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild() == null ? "PrivateMessage": context.getGuild().getId()); if (context instanceof SlashCommandContext slashCommandContext) logger.debug("SlashCommandInteractionId: {}", slashCommandContext.getEvent().getInteraction().getId()); - configureSentry(command); - try { + configureSentry(command); if (context instanceof MessageCommandContext && command.needAdmin() && !UserFunction.isAdmin(context.getGuildUser())) { context.reply("You are not powerful enough, scrub"); return; } - if (command.canBeExecutedBy(context.getGuild(), context.getGuildUser())) { - if (command.canBeExecutedFrom(context.getGuild(), context.getChannel())) { + if (command.canBeExecutedBy(context.getGuildUser())) { + if (command.canBeExecutedFrom(context.getChannel())) { CommandExecutionResult commandExecutionResult = null; if (context instanceof SlashCommandContext slashCommandContext) { @@ -65,7 +62,7 @@ public class CommandProcessor { } if (commandExecutionResult == null) { - throw new RuntimeException("Command type is not implemented."); + throw new RuntimeException("CommandExecutionResult is null"); } CommandExecutionResult.Result result = commandExecutionResult.getResult(); @@ -92,21 +89,25 @@ public class CommandProcessor { } } - private void configureSentry(AbstractCommand command) { - + private void configureSentry(Command command) { Sentry.configureScope(scope -> { - scope.setContexts("command", command.getCommandName()); - scope.setContexts("isAdmin", UserFunction.isAdmin(context.getGuildUser())); - scope.setContexts("canBeExecuted", command.canBeExecutedBy(context.getGuild(), context.getGuildUser())); + scope.setTag("command", command.getCommandName()); + scope.setTag("user", context.getGuildUser().getId()); scope.setTag("channel", context.getChannel().getId()); + if (context.getGuild() != null) { + scope.setTag("guild", context.getGuild().getId()); + scope.setTag("isAdmin", String.valueOf(UserFunction.isAdmin(context.getGuildUser()))); + scope.setTag("canBeExecutedBy", String.valueOf(command.canBeExecutedBy(context.getGuildUser()))); + } + if (context instanceof SlashCommandContext slashCommandContext) scope.setTag("interactionId", slashCommandContext.getEvent().getInteraction().getId()); if (context instanceof MessageCommandContext messageCommandContext) scope.setTag("message", messageCommandContext.getMessage().getId()); - scope.setTag("guild", context.getGuild().getId()); + }); } } diff --git a/src/main/java/me/bobert/netneutrality/core/context/AbstractContext.java b/src/main/java/me/bobert/netneutrality/core/context/AbstractContext.java new file mode 100644 index 0000000..7d45998 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/context/AbstractContext.java @@ -0,0 +1,61 @@ +package me.bobert.netneutrality.core.context; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import io.sentry.Sentry; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; + +public abstract class AbstractContext implements Context { + private final JDA jda; + private final Guild guild; + private final MessageChannel channel; + private final Member member; + private final User user; + + public AbstractContext(JDA jda, Guild guild, MessageChannel channel, Member member, User user) { + this.jda = jda; + this.guild = guild; + this.channel = channel; + this.member = member; + this.user = user; + + if (user != null) { + io.sentry.protocol.User sentryUser = new io.sentry.protocol.User(); + sentryUser.setId(user.getId()); + sentryUser.setUsername(user.getEffectiveName()); + Sentry.setUser(sentryUser); + } + } + + @Override + public JDA getJDA() { + return jda; + } + + @Override + public Guild getGuild() { + return guild; + } + + @Override + public MessageChannel getChannel() { + return channel; + } + + @Override + public Member getGuildUser() { + return member; + } + + @Override + public User getDiscordUser() { + return user; + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java b/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java index 74d7abb..42a82df 100644 --- a/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java +++ b/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java @@ -1,60 +1,34 @@ package me.bobert.netneutrality.core.context; - /* * Copyright (c) anthorob * * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.command.AbstractCommand; +import me.bobert.netneutrality.command.Command; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.events.Event; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -public interface CommandContext { +public abstract class CommandContext extends AbstractContext { + private Command command; - AbstractCommand getCommand(); - - Event getEvent(); - - Guild getGuild(); - - MessageChannel getChannel(); - - Member getGuildUser(); - - User getDiscordUser(); - - JDA getJDA(); - - void deleteMessage(); - - void reply(MessageCreateData message); - - void reply(String message); - - void reply(MessageEmbed... embeds); - - default void reply(String format, Object... arguments) { - reply(String.format(format, arguments)); + public CommandContext(JDA jda, Guild guild, MessageChannel channel, Member member, User user) { + super(jda, guild, channel, member, user); } - void replyToMessage(String message); - - default void replyToMessage(MessageCreateData message) { - reply(message); + public Command getCommand() { + return command; } - default void replyToMessage(String format, Object... arguments) { - replyToMessage(String.format(format, arguments)); + public void setCommand(Command command) { + this.command = command; } - default void replyToMessage(MessageEmbed... embeds) { - replyToMessage(MessageCreateData.fromEmbeds(embeds)); - } + public abstract String parseCommandName(); + + public abstract String getSubCommand(); + } diff --git a/src/main/java/me/bobert/netneutrality/core/context/Context.java b/src/main/java/me/bobert/netneutrality/core/context/Context.java new file mode 100644 index 0000000..287ea07 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/context/Context.java @@ -0,0 +1,161 @@ +package me.bobert.netneutrality.core.context; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.AsyncMessage; +import me.bobert.netneutrality.core.GuildManager; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.events.Event; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.dv8tion.jda.api.utils.messages.MessageEditData; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +public interface Context { + Event getEvent(); + + JDA getJDA(); + + InteractionHook getInteractionHook(); + + Guild getGuild(); + + MessageChannel getChannel(); + + Member getGuildUser(); + + User getDiscordUser(); + + boolean isAcknowledged(); + + void deleteMessage(); + + void reply(Modal modal); + + void reply(MessageCreateData message, boolean ephemeral, Consumer consumer, LayoutComponent... components); + + void defer(Consumer consumer); + + void ack(boolean ephemeral); + + default GuildManager getGuildManager() { + return GuildManager.getManager(getGuild()); + } + + default void ack() { + ack(false); + } + + default void ephemeral() { + ack(true); + } + + default void reply(MessageCreateData message) { + reply(message, false, asyncMessage -> {}); + } + + default void reply(String message) { + reply(MessageCreateData.fromContent(message)); + } + + default void reply(MessageEmbed... embeds) { + reply(MessageCreateData.fromEmbeds(embeds)); + } + + default void reply(String format, Object... arguments) { + reply(String.format(format, arguments)); + } + + default void edit(MessageEditData message, boolean ephemeral, Consumer consumer, LayoutComponent... components) { + if (isAcknowledged() && getInteractionHook() != null) { + getInteractionHook().editOriginal(message).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1))); + return; + } + + reply(MessageCreateData.fromEditData(message), ephemeral, consumer, components); + } + + default void edit(MessageEditData message, LayoutComponent... components) { + edit(message, false, asyncMessage -> {}, components); + } + + default void edit(MessageEditData message) { + edit(message, false, asyncMessage -> {}); + } + + default void edit(String message) { + edit(MessageEditData.fromContent(message)); + } + + default void edit(MessageEmbed embed) { + edit(MessageEditData.fromEmbeds(embed)); + } + + default void edit(MessageEmbed embeds, LayoutComponent... components) { + edit(MessageEditData.fromEmbeds(embeds), components); + } + + default void edit(String format, Object... arguments) { + edit(String.format(format, arguments)); + } + + default void async(MessageCreateData message, Consumer consumer) { + reply(message, false, consumer); + } + + default void async(String message, Consumer consumer) { + async(MessageCreateData.fromContent(message), consumer); + } + + default void async(Consumer consumer) { + defer(consumer); + } + + default void replyEphemeral(MessageCreateData message) { + reply(message, true, asyncMessage -> {}); + } + + default void replyEphemeral(String message) { + replyEphemeral(MessageCreateData.fromContent(message)); + } + + default void replyEphemeral(String format, Object... arguments) { + replyEphemeral(String.format(format, arguments)); + } + + default void replyEphemeral(MessageEmbed... embeds) { + replyEphemeral(MessageCreateData.fromEmbeds(embeds)); + } + + default void sendMessageInPrivate(MessageCreateData msg) { + User user = getDiscordUser(); + if (user != null) { + user.openPrivateChannel().flatMap(channel -> channel.sendMessage(msg)).queue(); + } + } + + default void sendMessageInPrivate(String message) { + sendMessageInPrivate(MessageCreateData.fromContent(message)); + } + + default void sendMessageInPrivate(MessageEmbed... embeds) { + sendMessageInPrivate(Arrays.asList(embeds)); + } + + default void sendMessageInPrivate(List embeds) { + sendMessageInPrivate(MessageCreateData.fromEmbeds(embeds)); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/context/MessageCommandContext.java b/src/main/java/me/bobert/netneutrality/core/context/MessageCommandContext.java deleted file mode 100644 index 9ef63af..0000000 --- a/src/main/java/me/bobert/netneutrality/core/context/MessageCommandContext.java +++ /dev/null @@ -1,192 +0,0 @@ -package me.bobert.netneutrality.core.context; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import io.sentry.Breadcrumb; -import io.sentry.Sentry; -import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.thread.ThreadManager; -import me.bobert.netneutrality.utils.Settings; -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; - -import java.util.Collection; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; - -public class MessageCommandContext implements CommandContext { - - private static final ExecutorService commandExecutor = ThreadManager.getExecutor(); - - private AbstractCommand command; - private final MessageReceivedEvent event; - private final Message message; - private final Guild guild; - private final MessageChannel channel; - private final Member member; - private final User user; - private final JDA jda; - - public MessageCommandContext(MessageReceivedEvent event, Message message, Member member, User user, Guild guild, MessageChannel channel, JDA jda) { - this.event = event; - this.message = message; - this.member = member; - this.guild = guild; - this.channel = channel; - this.user = user; - this.jda = jda; - - io.sentry.protocol.User sentryUser = new io.sentry.protocol.User(); - sentryUser.setId(member.getId()); - sentryUser.setUsername(member.getEffectiveName()); - Sentry.setUser(sentryUser); - Sentry.addBreadcrumb(Breadcrumb.info("New context for message id " + message.getId())); - } - - public static MessageCommandContext createContext(MessageReceivedEvent event) { - Message message = event.getMessage(); - Member member = event.getMember(); - User user = event.getAuthor(); - Guild guild = event.getGuild(); - MessageChannel channel = event.getChannel(); - JDA jda = event.getJDA(); - - return new MessageCommandContext(event, message, member, user, guild, channel, jda); - } - - @Override - public MessageReceivedEvent getEvent() { - return event; - } - - public String getArgument(int index) { - return getArgs()[index]; - } - - public String[] getArgs() { - String msg = message.getContentDisplay().trim(); - msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim(); - String cmd = msg.split(" ", 2)[0]; - String[] args = msg.replaceFirst("(" + cmd + ")(\\s+)?", "").split("\s"); - - if (args.length == 1 && args[0].isEmpty()) - args = new String[0]; - - return args; - } - - public String getMessageText() { - String msg = message.getContentDisplay().trim(); - msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim(); - String cmd = msg.split(" ", 2)[0]; - - return msg.replaceFirst("(" + cmd + ")(\s)?", ""); - } - - public Message getMessage() { - return message; - } - - public Guild getGuild() { - return guild; - } - - public MessageChannel getChannel() { - return channel; - } - - public Member getGuildUser() { - return member; - } - - public User getDiscordUser() { - return user; - } - - public JDA getJDA() { - return jda; - } - - public AbstractCommand getCommand() { - return command; - } - - public GuildManager getGuildManager() { - return GuildManager.getManager(guild); - } - - public void reply(String message) { - reply(MessageCreateData.fromContent(message)); - } - - public void reply(String format, Object... arguments) { - reply(String.format(format, arguments)); - } - - public void reply(MessageEmbed... embeds) { - reply(MessageCreateData.fromEmbeds(embeds)); - } - - public void reply(MessageCreateData msg) { - if (channel != null && channel.canTalk()) { - channel.sendMessage(msg).queue(); - } - } - - @Override - public void replyToMessage(String message) { - replyToMessage(MessageCreateData.fromContent(message)); - } - - @Override - public void replyToMessage(String format, Object... arguments) { - replyToMessage(String.format(format, arguments)); - } - - public void replyToMessage(MessageCreateData msg) { - if (channel != null && channel.canTalk() && message != null) { - message.reply(msg).queue(); - } - } - - public void replyPrivate(String message) { - replyPrivate(MessageCreateData.fromContent(message)); - } - - public void replyPrivate(MessageCreateData msg) { - user.openPrivateChannel().flatMap(channel -> channel.sendMessage(msg)).queue(); - } - - public void replyPrivate(MessageEmbed... embeds) { - replyPrivate(MessageCreateData.fromEmbeds(embeds)); - } - - public void replyPrivate(Collection embeds) { - user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embeds)).queue(); - } - - @Override - public void deleteMessage() { - if (message != null) { - message.delete().queueAfter(1, TimeUnit.SECONDS); - } - } - - public boolean isMessageFromOwner() { - return member.isOwner(); - } - - public boolean isMessageFromCreator() { - String creatorId = Settings.getInstance().get("NETNEUTRALITY_BOT_OWNER"); - - return creatorId.equals(member.getId()); - } - -} diff --git a/src/main/java/me/bobert/netneutrality/core/context/SlashCommandContext.java b/src/main/java/me/bobert/netneutrality/core/context/SlashCommandContext.java deleted file mode 100644 index 35064c1..0000000 --- a/src/main/java/me/bobert/netneutrality/core/context/SlashCommandContext.java +++ /dev/null @@ -1,180 +0,0 @@ -package me.bobert.netneutrality.core.context; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import me.bobert.netneutrality.command.AbstractCommand; -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.InteractionHook; -import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -import java.util.Collection; - -public class SlashCommandContext implements CommandContext { - - private final SlashCommandInteractionEvent event; - private AbstractCommand command; - private final JDA jda; - private final Guild guild; - private final MessageChannel channel; - private final Member member; - private final User user; - - private SlashCommandContext(JDA jda, SlashCommandInteractionEvent event, Guild guild, MessageChannel channel, Member member, User user) { - this.jda = jda; - this.event = event; - this.guild = guild; - this.channel = channel; - this.member = member; - this.user = user; - } - - public static SlashCommandContext createContext(SlashCommandInteractionEvent event) { - Member member = event.getMember(); - User user = event.getUser(); - Guild guild = event.getGuild(); - MessageChannel channel = event.getChannel(); - JDA jda = event.getJDA(); - - return new SlashCommandContext(jda, event, guild, channel, member, user); - } - - @Override - public SlashCommandInteractionEvent getEvent() { - return event; - } - - @Override - public AbstractCommand getCommand() { - return command; - } - - @Override - public JDA getJDA() { - return jda; - } - - @Override - public Guild getGuild() { - return guild; - } - - @Override - public MessageChannel getChannel() { - return channel; - } - - @Override - public Member getGuildUser() { - return member; - } - - @Override - public User getDiscordUser() { - return user; - } - - @Override - public void deleteMessage() { - // not implemented - } - - public void sendMessageInPrivate(String msg) { - sendMessageInPrivate(new MessageCreateBuilder().setContent(msg).build()); - } - - public void sendMessageInPrivate(MessageCreateData msg) { - user.openPrivateChannel().flatMap(channel -> channel.sendMessage(msg)).queue(); - } - - public void sendMessageInPrivate(MessageEmbed embed, MessageEmbed... embeds) { - user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embed, embeds)).queue(); - } - - public void sendMessageInPrivate(Collection embeds) { - user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embeds)).queue(); - } - - public void reply(MessageCreateData message) { - if (event.isAcknowledged()) { - getHook().editOriginal(MessageEditData.fromCreateData(message)).queue(); - //getHook().sendMessage(message).setEphemeral(false).queue(); - return; - } - - event.reply(message).queue(); - } - - public void reply(MessageEmbed... embeds) { - reply(MessageCreateData.fromEmbeds(embeds)); - } - - @Override - public void reply(String message) { - reply(MessageCreateData.fromContent(message)); - } - - public void replyEphemeral(MessageCreateData message) { - if (event.isAcknowledged()) { - getHook().editOriginal(MessageEditData.fromCreateData(message)).queue(); - //getHook().sendMessage(message).setEphemeral(true).queue(); - return; - } - - event.reply(message).setEphemeral(true).queue(); - } - - public void replyEphemeral(MessageEmbed... embeds) { - replyEphemeral(MessageCreateData.fromEmbeds(embeds)); - } - - public void replyEphemeral(String format, Object... arguments) { - replyEphemeral(String.format(format, arguments)); - } - - public void replyEphemeral(String message) { - replyEphemeral(MessageCreateData.fromContent(message)); - } - - @Override - public void replyToMessage(String message) { - replyEphemeral(message); - } - - public InteractionHook getHook() { - if (!event.isAcknowledged()) - throw new RuntimeException("Interaction isn't acknowledged yet"); - - return event.getHook(); - } - - public void ackAndWait(boolean ephemeral) { - if (event.isAcknowledged()) - throw new RuntimeException("Interaction is already acknowledged, please use getHook() instead."); - - event.deferReply(ephemeral).queue(); - } - - public void ackAndWait() { - ackAndWait(false); - } - - public void ack() { - ackAndWait(); - } - - public void ephemeral() { - ackAndWait(true); - } - -} diff --git a/src/main/java/me/bobert/netneutrality/core/context/TaskContext.java b/src/main/java/me/bobert/netneutrality/core/context/TaskContext.java new file mode 100644 index 0000000..391726f --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/context/TaskContext.java @@ -0,0 +1,32 @@ +package me.bobert.netneutrality.core.context; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.interactions.modals.Modal; + +public abstract class TaskContext extends AbstractContext { + + public TaskContext(JDA jda, Guild guild, MessageChannel channel, Member member, User user) { + super(jda, guild, channel, member, user); + } + + @Override + public void deleteMessage() { + + } + + @Override + public void reply(Modal modal) { + + } + + public abstract String getId(); +} diff --git a/src/main/java/me/bobert/netneutrality/core/db/DatabaseMigration.java b/src/main/java/me/bobert/netneutrality/core/db/DatabaseMigration.java new file mode 100644 index 0000000..fe846e5 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/db/DatabaseMigration.java @@ -0,0 +1,34 @@ +package me.bobert.netneutrality.core.db; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.utils.Settings; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.flywaydb.core.Flyway; + +public class DatabaseMigration { + private final static Logger logger = LogManager.getLogger(DatabaseMigration.class); + + public static void migrate() { + String dbName = Settings.getInstance().get("MYSQL_DATABASE", "netneutrality"); + String dbUser = Settings.getInstance().get("MYSQL_USER", "root"); + String dbPass = Settings.getInstance().get("MYSQL_PASSWORD", ""); + String dbHost = Settings.getInstance().get("MYSQL_HOST", "localhost"); + + try { + Flyway flyway = Flyway.configure() + .dataSource(String.format("jdbc:mysql://%s:3306/%s", dbHost, dbName), dbUser, dbPass) + .baselineOnMigrate(true) + .load(); + + flyway.migrate(); + } catch (Exception e) { + logger.error(e); + System.exit(1); + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/SessionManager.java b/src/main/java/me/bobert/netneutrality/core/db/SessionManager.java similarity index 86% rename from src/main/java/me/bobert/netneutrality/core/SessionManager.java rename to src/main/java/me/bobert/netneutrality/core/db/SessionManager.java index fab89e5..110a13f 100644 --- a/src/main/java/me/bobert/netneutrality/core/SessionManager.java +++ b/src/main/java/me/bobert/netneutrality/core/db/SessionManager.java @@ -1,4 +1,4 @@ -package me.bobert.netneutrality.core; +package me.bobert.netneutrality.core.db; /* * Copyright (c) anthorob * @@ -20,7 +20,7 @@ import java.util.HashMap; import java.util.Map; public class SessionManager { - private final static Logger logger = LogManager.getLogger(SessionManager.class); + private static final Logger logger = LogManager.getLogger(SessionManager.class); private static StandardServiceRegistry registry; private static SessionFactory sessionFactory; @@ -35,9 +35,9 @@ public class SessionManager { String dbHost = Settings.getInstance().get("MYSQL_HOST", "localhost"); logger.info(String.format("Connecting to database '%s' at %s:3306 with user '%s'", dbName, dbHost, dbUser)); - settings.put(Environment.URL, String.format("jdbc:mysql://%s:3306/%s", dbHost, dbName)); - settings.put(Environment.USER, dbUser); - settings.put(Environment.PASS, dbPass); + settings.put(Environment.JAKARTA_JDBC_URL, String.format("jdbc:mysql://%s:3306/%s", dbHost, dbName)); + settings.put(Environment.JAKARTA_JDBC_USER, dbUser); + settings.put(Environment.JAKARTA_JDBC_PASSWORD, dbPass); registryBuilder.applySettings(settings); registry = registryBuilder.build(); diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/AbstractPersistableTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractPersistableTask.java new file mode 100644 index 0000000..0edcbee --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractPersistableTask.java @@ -0,0 +1,39 @@ +package me.bobert.netneutrality.core.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.CommandContext; +import org.json.JSONObject; + +public abstract class AbstractPersistableTask extends AbstractTask implements PersistableTask { + + @Override + public boolean isInsertable() { + return true; + } + + @Override + public boolean oneshot() { + return false; + } + + @Override + public boolean isExpired() { + return false; + } + + @Override + public void replyToInteraction(CommandContext context) { + + } + + @Override + public String toPayload() { + JSONObject payload = new JSONObject(this); + + return payload.toString(); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/AbstractTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractTask.java new file mode 100644 index 0000000..6da8833 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/AbstractTask.java @@ -0,0 +1,46 @@ +package me.bobert.netneutrality.core.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import java.time.OffsetDateTime; +import java.util.Objects; + +public abstract class AbstractTask implements Task { + private final OffsetDateTime creationTime; + + public AbstractTask() { + creationTime = OffsetDateTime.now(); + } + + @Override + public boolean isInsertable() { + return true; + } + + @Override + public OffsetDateTime getCreationTime() { + return creationTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AbstractTask that = (AbstractTask) o; + return Objects.equals(getId(), that.getId()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getId()); + } + + @Override + public String toString() { + return String.format("Task{id=%s, class=%s, name=%s, oneshot=%b, creationTime=%s}", getId(), getClass().getSimpleName(), getName(), oneshot(), getCreationTime()); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java new file mode 100644 index 0000000..3d0179d --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java @@ -0,0 +1,14 @@ +package me.bobert.netneutrality.core.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +/** + * Represents a task that will be saved in the database and loaded when the bot is started + */ +public interface PersistableTask extends Task { + + String toPayload(); +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/Task.java b/src/main/java/me/bobert/netneutrality/core/tasks/Task.java new file mode 100644 index 0000000..88c2699 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/Task.java @@ -0,0 +1,62 @@ +package me.bobert.netneutrality.core.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.trait.WithChildren; + +import java.time.OffsetDateTime; + +public interface Task { + + /** + *

This method should generate a unique ID to differentiate it from other tasks.

+ *

+ * For button and modal interaction, this is the ID of the component sent to discord. + * For other types of interaction, for example emoji reaction, + * each reaction should get a unique ID generated by the context. + *

+ * @return a unique ID for this task + */ + String getId(); + + /** + * This is only used for debugging purpose. + * @return Name of the task + */ + String getName(); + + /** + *

If this returns false, the task will not get inserted and executed.

+ */ + boolean isInsertable(); + + /** + * This determines if the task should be cancelled after execution. + */ + boolean oneshot(); + + /** + *

This method is used to reply to the interaction that triggered this task.

+ *

If the task is implementing {@link WithChildren}, the child tasks must be instanciated in this method.

+ */ + void replyToInteraction(CommandContext context); + + /** + * Executes the task. + * @param context The context of a task containing the event that triggererd it. + */ + void execute(TaskContext context); + + /** + * When this returns true, the task will get removed by the cleanup thread. + * @return If the task is expired + */ + boolean isExpired(); + + OffsetDateTime getCreationTime(); +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/TaskManager.java b/src/main/java/me/bobert/netneutrality/core/tasks/TaskManager.java new file mode 100644 index 0000000..54de4a9 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/TaskManager.java @@ -0,0 +1,273 @@ +package me.bobert.netneutrality.core.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.ButtonTaskContext; +import me.bobert.netneutrality.context.tasks.ModalTaskContext; +import me.bobert.netneutrality.core.LogHandler; +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.persistence.TaskRegistry; +import me.bobert.netneutrality.core.tasks.trait.Deletable; +import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable; +import me.bobert.netneutrality.core.tasks.trait.WithChildren; +import me.bobert.netneutrality.core.tasks.trait.WithParent; +import me.bobert.netneutrality.core.thread.ThreadManager; +import me.bobert.netneutrality.db.entities.OTask; +import me.bobert.netneutrality.db.repository.TaskRepository; +import me.bobert.netneutrality.exceptions.TaskExecutionException; +import me.bobert.netneutrality.thread.CleanupTaskThread; +import net.dv8tion.jda.api.interactions.InteractionHook; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class TaskManager { + private static final Logger logger = LogManager.getLogger(TaskManager.class); + private static final ScheduledExecutorService executor = ThreadManager.getScheduledExecutor(); + private static TaskManager instance; + private final TaskRegistry taskRegistry; + private final Map tasks; + private final Map persistableTasks; + private final Map deletableTasksContext; + private ScheduledFuture cleanupThread; + + private TaskManager() { + this.tasks = new HashMap<>(); + this.persistableTasks = new HashMap<>(); + this.deletableTasksContext = new HashMap<>(); + this.taskRegistry = new TaskRegistry(); + } + + public static TaskManager getInstance() { + if (instance == null) { + instance = new TaskManager(); + } + + return instance; + } + + public void init() { + taskRegistry.loadTasks(); + } + + public Task getTask(String id) { + Task task = tasks.get(id); + + if (task == null) { + task = persistableTasks.get(id); + } + + return task; + } + + public Collection getTasks() { + return tasks.values(); + } + + public Map getPersistableTaskMap() { + return persistableTasks; + } + + public boolean exists(String id) { + boolean exists = tasks.containsKey(id); + + if (!exists) { + exists = persistableTasks.containsKey(id); + } + + return exists; + } + + public void insert(CommandContext context, Task task) { + String id = task.getId(); + + if (tasks.containsKey(id) || persistableTasks.containsKey(id)) { + context.reply("A task with the same id already exists!"); + return; + } + + task.replyToInteraction(context); + + if (task instanceof Deletable) { + deletableTasksContext.put(task.getId(), context); + } + + if (task.isInsertable()) { + logger.info("Inserting task: {}", task); + + if (task instanceof PersistableTask) { + persistTask((PersistableTask) task); + persistableTasks.put(id, (PersistableTask) task); + } else { + tasks.put(id, task); + } + } + + if (task instanceof WithChildren taskWithChildren) { + List childrens = taskWithChildren.getChildTasks(); + + for (Task child : childrens) { + insert(context, child); + } + } + + if (!tasks.isEmpty() && cleanupThread == null) { + cleanupThread = executor.scheduleWithFixedDelay(new CleanupTaskThread(), 1, 5, TimeUnit.MINUTES); + } + } + + public void removeTask(Task task) { + String id = task.getId(); + + if (tasks.containsKey(id)) { + logger.debug("Removing task: {}", task); + tasks.remove(id); + } + + if (tasks.isEmpty() && cleanupThread != null) { + cleanupThread.cancel(false); + cleanupThread = null; + } + } + + public void handle(TaskContext context) { + Task task = tasks.get(context.getId()); + + if (task == null) { + task = persistableTasks.get(context.getId()); + } + + if (task == null && (context instanceof ModalTaskContext || context instanceof ButtonTaskContext)) { + context.replyEphemeral("This task is no longer available, someone might have already used it or it might have expired"); + } + + if (task != null) { + logger.debug("Executing task: {}", task); + + try { + // Cancel task during execution to prevent spam + removeTaskAndChildTasks(task); + + task.execute(context); + + if (task.isInsertable() && !task.oneshot()) { + reinsertTaskAndChildTasks(task); + } + + deleteInteraction(task); + + // If interaction is still not acknowledged after execution, send a placeholder response. + if (!context.isAcknowledged() && !(task instanceof NotAcknowledgeable)) { + context.replyEphemeral("Task executed successfully but interaction wasn't acknowledged, report this bug by using the command /report-bug"); + } + + } catch (TaskExecutionException exception) { + LogHandler.log(exception); + } catch (Exception exception) { + LogHandler.log(new TaskExecutionException("Error during task execution", context, exception)); + } + } + } + + public void persistTask(PersistableTask task) { + String taskClass = task.getClass().getName(); + if (taskRegistry.getTaskCreator(taskClass) == null) { + logger.error("No TaskCreator found for class: {}", taskClass); + return; + } + + TaskRepository taskRepository = new TaskRepository(); + OTask oTask = taskRepository.findByTaskId(task.getId()); + if (oTask == null) { + oTask = new OTask(); + } + + oTask.setTaskId(task.getId()); + oTask.setTaskClass(task.getClass().getName()); + oTask.setPayload(task.toPayload()); + + taskRepository.save(oTask); + } + + public void unpersistTask(PersistableTask task) { + logger.debug("Unpersisting task: {}", task); + TaskRepository taskRepository = new TaskRepository(); + + taskRepository.deleteBy(task.getId()); + persistableTasks.remove(task.getId()); + } + + private void deleteInteraction(Task task) { + if (task instanceof WithParent taskWithParent) { + Task parent = (Task) taskWithParent.getParentTask(); + + if (parent instanceof Deletable deletable && task.isInsertable()) { + deleteInteraction(parent.getId(), deletable); + return; + } + } + + if (task instanceof Deletable deletable && task.isInsertable()) { + deleteInteraction(task.getId(), deletable); + } + } + + private void deleteInteraction(String taskId, Deletable deletable) { + logger.debug("Deleting interaction: {}", deletable); + + CommandContext context = deletableTasksContext.get(taskId); + if (context != null) { + deletableTasksContext.remove(taskId); + + InteractionHook hook = context.getInteractionHook(); + if (hook != null) { + if (hook.isExpired()) { + context.replyEphemeral("This interaction has expired, can't remove it"); + } + + if (!hook.isExpired()) { + hook.deleteOriginal().queue(); + } + } + } + + deletable.onInteractionDelete(); + } + + private void removeTaskAndChildTasks(Task task) { + tasks.remove(task.getId()); + + if (task instanceof WithParent taskWithParent && !taskWithParent.shouldKeepChilds()) { + List childrens = taskWithParent.getParentTask().getChildTasks(); + for (Task child : childrens) { + if (!task.equals(child)) { + tasks.remove(child.getId()); + } + } + } + } + + private void reinsertTaskAndChildTasks(Task task) { + tasks.put(task.getId(), task); + + if (task instanceof WithParent taskWithParent && taskWithParent.shouldKeepChilds()) { + List childrens = taskWithParent.getParentTask().getChildTasks(); + for (Task child : childrens) { + if (!task.equals(child)) { + tasks.put(child.getId(), child); + } + } + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskFactory.java b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskFactory.java new file mode 100644 index 0000000..7126c1e --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskFactory.java @@ -0,0 +1,69 @@ +package me.bobert.netneutrality.core.tasks.persistence; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.tasks.PersistableTask; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.Map; + +public class TaskFactory { + private static final Logger logger = LogManager.getLogger(TaskFactory.class); + + public static PersistableTask createTaskInstance(String className, JSONArray params, JSONArray types, JSONObject payload) throws Exception { + if (params.length() != types.length()) { + throw new RuntimeException("Wrong number of parameters and types for " + className); + } + + Class clazz = Class.forName(className); + + Class[] classTypes = new Class[types.length()]; + Object[] classParams = new Object[params.length()]; + + for (int i = 0; i < params.length(); i++) { + String type = types.getString(i); + String param = params.getString(i); + + Class classType = parseClassTypeFromString(type); + Object classParam = castParamToType(classType, param, type, payload); + + classTypes[i] = classType; + classParams[i] = classParam; + } + + return (PersistableTask) clazz.getConstructor(classTypes).newInstance(classParams); + } + + private static Class parseClassTypeFromString(String param) { + return switch (param) { + case "string" -> String.class; + case "int" -> Integer.class; + case "map" -> Map.class; + default -> throw new IllegalArgumentException("Param " + param + " is not implemented yet."); + }; + } + + private static Object castParamToType(Class type, String paramName, String typeString, JSONObject payload) { + if (type == String.class) { + return payload.getString(paramName); + } + + if (type == Integer.class) { + return payload.getInt(paramName); + } + + if (type == Map.class) { + JSONObject mapJson = payload.getJSONObject(paramName); + + return mapJson.toMap(); + } + + throw new IllegalArgumentException("Type " + typeString + " is not implemented yet."); + } +} \ No newline at end of file diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskRegistry.java b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskRegistry.java new file mode 100644 index 0000000..74e7ca2 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/TaskRegistry.java @@ -0,0 +1,101 @@ +package me.bobert.netneutrality.core.tasks.persistence; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.tasks.PersistableTask; +import me.bobert.netneutrality.core.tasks.TaskManager; +import me.bobert.netneutrality.db.entities.OTask; +import me.bobert.netneutrality.db.repository.TaskRepository; +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TaskRegistry { + @FunctionalInterface + public interface TaskCreator { + PersistableTask fromPayload(JSONObject payload); + } + + private static final Logger logger = LogManager.getLogger(TaskRegistry.class); + private final Map taskCreators = new HashMap<>(); + private final TaskRepository taskRepository; + + public TaskRegistry() { + taskRepository = new TaskRepository(); + loadTaskConfig(); + } + + public TaskCreator getTaskCreator(String className) { + return taskCreators.get(className); + } + + private void loadTaskConfig() { + String fileName = "persistable-tasks.json"; + + try (InputStream is = TaskFactory.class.getClassLoader().getResourceAsStream(fileName)) { + if (is == null) { + throw new FileNotFoundException("Could not load: " + fileName); + } + + String jsonString = IOUtils.toString(is, StandardCharsets.UTF_8); + JSONObject json = new JSONObject(jsonString); + + json.getJSONArray("tasks").forEach(task -> { + JSONObject taskJson = (JSONObject) task; + + String className = taskJson.getString("class"); + JSONArray params = taskJson.getJSONArray("params"); + JSONArray types = taskJson.getJSONArray("types"); + + taskCreators.put(className, payload -> { + try { + return TaskFactory.createTaskInstance(className, params, types, payload); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + }); + } catch (Exception e) { + logger.error(e); + } + } + + public void loadTasks() { + logger.info("Loading persistable tasks..."); + List tasks = taskRepository.findAll(); + + for (OTask oTask : tasks) { + String taskClass = oTask.getTaskClass(); + String payload = oTask.getPayload(); + + try { + TaskCreator creator = getTaskCreator(taskClass); + if (creator == null) { + throw new IllegalArgumentException("No task creator registered for class: " + taskClass); + } + + JSONObject json = new JSONObject(payload); + PersistableTask task = creator.fromPayload(json); + + logger.info("Loaded task: {}", task); + TaskManager.getInstance().getPersistableTaskMap().put(task.getId(), task); + } catch (Exception e) { + logger.error("Error loading task: {}", payload, e); + } + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/trait/Deletable.java b/src/main/java/me/bobert/netneutrality/core/tasks/trait/Deletable.java new file mode 100644 index 0000000..bbd9715 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/trait/Deletable.java @@ -0,0 +1,13 @@ +package me.bobert.netneutrality.core.tasks.trait; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +/** + * Represents a task that will remove its interaction from the channel after execution + */ +public interface Deletable { + void onInteractionDelete(); +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/trait/NotAcknowledgeable.java b/src/main/java/me/bobert/netneutrality/core/tasks/trait/NotAcknowledgeable.java new file mode 100644 index 0000000..786325b --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/trait/NotAcknowledgeable.java @@ -0,0 +1,12 @@ +package me.bobert.netneutrality.core.tasks.trait; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +/** + * If a task implements this interface, it means that the interaction will not get acknowledged during execution. + */ +public interface NotAcknowledgeable { +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithChildren.java b/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithChildren.java new file mode 100644 index 0000000..2a09fe4 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithChildren.java @@ -0,0 +1,22 @@ +package me.bobert.netneutrality.core.tasks.trait; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.tasks.Task; +import me.bobert.netneutrality.core.tasks.TaskManager; + +import java.util.List; + +public interface WithChildren { + + List getChildTasks(); + + default void removeAllChildTasks() { + for (Task child : getChildTasks()) { + TaskManager.getInstance().removeTask(child); + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithParent.java b/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithParent.java new file mode 100644 index 0000000..6fe6074 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/tasks/trait/WithParent.java @@ -0,0 +1,16 @@ +package me.bobert.netneutrality.core.tasks.trait; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +public interface WithParent { + + E getParentTask(); + + /** + * Whether execution of this task should remove all childs of its parent task during execution + */ + boolean shouldKeepChilds(); +} diff --git a/src/main/java/me/bobert/netneutrality/thread/BaseThreadFactory.java b/src/main/java/me/bobert/netneutrality/core/thread/BaseThreadFactory.java similarity index 94% rename from src/main/java/me/bobert/netneutrality/thread/BaseThreadFactory.java rename to src/main/java/me/bobert/netneutrality/core/thread/BaseThreadFactory.java index 1c33201..d0b5c16 100644 --- a/src/main/java/me/bobert/netneutrality/thread/BaseThreadFactory.java +++ b/src/main/java/me/bobert/netneutrality/core/thread/BaseThreadFactory.java @@ -1,4 +1,4 @@ -package me.bobert.netneutrality.thread; +package me.bobert.netneutrality.core.thread; /* * Copyright (c) anthorob diff --git a/src/main/java/me/bobert/netneutrality/thread/ThreadManager.java b/src/main/java/me/bobert/netneutrality/core/thread/ThreadManager.java similarity index 50% rename from src/main/java/me/bobert/netneutrality/thread/ThreadManager.java rename to src/main/java/me/bobert/netneutrality/core/thread/ThreadManager.java index f2d88e7..02b92f5 100644 --- a/src/main/java/me/bobert/netneutrality/thread/ThreadManager.java +++ b/src/main/java/me/bobert/netneutrality/core/thread/ThreadManager.java @@ -1,4 +1,4 @@ -package me.bobert.netneutrality.thread; +package me.bobert.netneutrality.core.thread; /* * Copyright (c) anthorob * @@ -10,40 +10,23 @@ import java.util.concurrent.*; public class ThreadManager { - public enum ExecutorType { - SINGLE, SCHEDULED, FIXED - } - private static final Map executors = new ConcurrentHashMap<>(); - public static ScheduledExecutorService getMusicExecutor() { + public static ScheduledExecutorService getScheduledExecutor() { return (ScheduledExecutorService) executors.computeIfAbsent( - "music-executor", + "scheduled-executor", s -> Executors.newScheduledThreadPool( Runtime.getRuntime().availableProcessors(), - new BaseThreadFactory("music-executor") + new BaseThreadFactory(s) ) ); } public static ExecutorService getExecutor() { - return executors.computeIfAbsent("general-executor", s -> Executors.newCachedThreadPool(new BaseThreadFactory("general-executor"))); - } - - public static ExecutorService getExecutor(String name) { - return executors.computeIfAbsent(name, s -> Executors.newSingleThreadExecutor(new BaseThreadFactory(name))); - } - - public static ExecutorService getExecutor(String name, ExecutorType type) { - ExecutorService executor; - - switch (type) { - case FIXED -> executor = executors.computeIfAbsent(name, s -> Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new BaseThreadFactory(name))); - case SCHEDULED -> executor = executors.computeIfAbsent(name, s -> Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new BaseThreadFactory(name))); - default -> executor = executors.computeIfAbsent(name, s -> Executors.newSingleThreadExecutor(new BaseThreadFactory(name))); - } - - return executor; + return executors.computeIfAbsent( + "general-executor", + s -> Executors.newCachedThreadPool(new BaseThreadFactory(s)) + ); } public static void shutdownExecutor() { diff --git a/src/main/java/me/bobert/netneutrality/db/AbstractController.java b/src/main/java/me/bobert/netneutrality/db/AbstractController.java deleted file mode 100644 index 6fad802..0000000 --- a/src/main/java/me/bobert/netneutrality/db/AbstractController.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.bobert.netneutrality.db; - -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import me.bobert.netneutrality.core.SessionManager; -import org.hibernate.SessionFactory; - -public abstract class AbstractController { - - protected SessionFactory sessionFactory; - - public AbstractController() { - sessionFactory = SessionManager.getSessionFactory(); - } - - protected SessionFactory getSessionFactory() { - return sessionFactory; - } -} diff --git a/src/main/java/me/bobert/netneutrality/db/DBController.java b/src/main/java/me/bobert/netneutrality/db/DBController.java deleted file mode 100644 index aab0723..0000000 --- a/src/main/java/me/bobert/netneutrality/db/DBController.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.bobert.netneutrality.db; - -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import me.bobert.netneutrality.core.SessionManager; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.lang.reflect.InvocationTargetException; - -public class DBController { - - private final static Logger logger = LogManager.getLogger(DBController.class); - - public static T get(Class returnType) { - if (returnType == null) - throw new IllegalArgumentException("return type is null"); - - try { - return returnType.getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - public static void shutdown() { - SessionManager.shutdown(); - } - - public static void init() { - SessionManager.getSessionFactory(); - } - -} diff --git a/src/main/java/me/bobert/netneutrality/db/GSettings.java b/src/main/java/me/bobert/netneutrality/db/GSettings.java index 5945ff5..1c4b276 100644 --- a/src/main/java/me/bobert/netneutrality/db/GSettings.java +++ b/src/main/java/me/bobert/netneutrality/db/GSettings.java @@ -9,7 +9,9 @@ public enum GSettings { //TODO: description MUSIC_ROLE("MUSIC_ROLE", ""), MUSIC_CHANNEL("MUSIC_CHANNEL", ""), - MUSIC_VOLUME("MUSIC_VOLUME", "30"); + MUSIC_VOLUME("MUSIC_VOLUME", "30"), + MUSIC_SEARCHRESULTS_LIMIT("MUSIC_SEARCHRESULTS_LIMIT", "5"), + MUSIC_MAXPLAYLISTSIZE("MUSIC_MAXPLAYLISTSIZE", "20"); private final String name; private final String defaultValue; diff --git a/src/main/java/me/bobert/netneutrality/db/Repository.java b/src/main/java/me/bobert/netneutrality/db/Repository.java new file mode 100644 index 0000000..cd9562c --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/db/Repository.java @@ -0,0 +1,42 @@ +package me.bobert.netneutrality.db; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.db.SessionManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.exception.ConstraintViolationException; + +import java.util.List; + +public interface Repository { + Logger logger = LogManager.getLogger(Repository.class); + + List findAll(); + + default void save(O entity) { + Transaction transaction = null; + + try(Session session = SessionManager.getSessionFactory().openSession()) { + transaction = session.beginTransaction(); + session.merge(entity); + transaction.commit(); + } catch (ConstraintViolationException e) { + logger.error(e); + throw e; + } catch (Exception e) { + logger.error(e); + + if (transaction != null) { + transaction.rollback(); + } + } + } + + void deleteBy(String id); +} diff --git a/src/main/java/me/bobert/netneutrality/db/controllers/DBBlacklist.java b/src/main/java/me/bobert/netneutrality/db/controllers/DBBlacklist.java deleted file mode 100644 index 19dea2a..0000000 --- a/src/main/java/me/bobert/netneutrality/db/controllers/DBBlacklist.java +++ /dev/null @@ -1,141 +0,0 @@ -package me.bobert.netneutrality.db.controllers; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.core.context.CommandContext; -import me.bobert.netneutrality.db.AbstractController; -import me.bobert.netneutrality.db.entities.OBlacklist; -import me.bobert.netneutrality.db.entities.OGuild; -import me.bobert.netneutrality.utils.JpaUtils; -import net.dv8tion.jda.api.entities.Guild; -import org.hibernate.Session; -import org.hibernate.query.Query; - -import java.util.ArrayList; - -public class DBBlacklist extends AbstractController { - - public void add(String word, CommandContext context) { - GuildManager guildManager = GuildManager.getManager(context.getGuild()); - - - OBlacklist bl = getWordFromDb(word, context.getGuild().getId()); - - if (bl == null) { - Session session = getSessionFactory().openSession(); - - session.beginTransaction(); - bl = new OBlacklist(); - bl.setGuild(guildManager.getGuildEntity()); - bl.setCreatorId(context.getGuildUser().getId()); - bl.setWord(word); - bl.setEnabled(true); - - session.persist(bl); - session.getTransaction().commit(); - session.close(); - - guildManager.reloadBlacklistCache(); - context.reply(String.format("Added the word [%s] to the blacklist.", word)); - } else { - context.reply("Word is already blacklisted"); - } - - - - } - - public void delete(String word, CommandContext context) { - GuildManager guildManager = GuildManager.getManager(context.getGuild()); - OBlacklist bl = getWordFromDb(word, context.getGuild().getId()); - - if (bl != null) { - Session session = getSessionFactory().openSession(); - session.beginTransaction(); - - session.remove(bl); - session.getTransaction().commit(); - session.close(); - - guildManager.reloadBlacklistCache(); - context.reply("Removed the word [%s] from the blacklist.", word); - } else { - context.reply("Can't delete word because it doesn't exist"); - } - } - - public void enable(String word, CommandContext context) { - GuildManager guildManager = GuildManager.getManager(context.getGuild()); - - - OBlacklist bl = getWordFromDb(word, context.getGuild().getId()); - if (bl != null && !bl.getEnabled()) { - Session session = getSessionFactory().openSession(); - session.beginTransaction(); - - bl.setEnabled(true); - - session.merge(bl); - session.getTransaction().commit(); - session.close(); - - guildManager.reloadBlacklistCache(); - context.reply("Enabled the word [%s] for the blacklist.", word); - } else { - context.reply("Either word doesn't exist or is already enabled"); - } - - - } - - public void disable(String word, CommandContext context) { - GuildManager guildManager = GuildManager.getManager(context.getGuild()); - - - OBlacklist bl = getWordFromDb(word, context.getGuild().getId()); - if (bl != null && bl.getEnabled()) { - Session session = getSessionFactory().openSession(); - session.beginTransaction(); - - bl.setEnabled(false); - - session.merge(bl); - session.getTransaction().commit(); - session.close(); - - guildManager.reloadBlacklistCache(); - context.reply("Disabled the word [%s] for the blacklist.", word); - } else { - context.reply("Either word doesn't exist or is already disabled"); - } - } - - public ArrayList getBlacklistForGuild(Guild guild) { - GuildManager guildManager = GuildManager.getManager(guild); - Session session = getSessionFactory().openSession(); - - OGuild guildEntity = guildManager.getGuildEntity(); - JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId()); - - ArrayList blacklists = new ArrayList<>(guildEntity.getBlacklists()); - session.close(); - - return blacklists; - } - - private OBlacklist getWordFromDb(String word, String guildId) { - Session session = getSessionFactory().openSession(); - - Query query = session.createNamedQuery("OBlacklist.findByWordEqualsIgnoreCaseAndGuild_GuildIdEquals", OBlacklist.class); - query.setParameter("guildId", guildId); - query.setParameter("word", word); - OBlacklist blacklist = query.uniqueResult(); - - session.close(); - return blacklist; - } -} diff --git a/src/main/java/me/bobert/netneutrality/db/controllers/DBMessage.java b/src/main/java/me/bobert/netneutrality/db/controllers/DBMessage.java deleted file mode 100644 index 81b1b38..0000000 --- a/src/main/java/me/bobert/netneutrality/db/controllers/DBMessage.java +++ /dev/null @@ -1,97 +0,0 @@ -package me.bobert.netneutrality.db.controllers; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.db.AbstractController; -import me.bobert.netneutrality.db.entities.OLogMessage; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.hibernate.Session; -import org.hibernate.query.Query; - -import java.util.List; - -public class DBMessage extends AbstractController { - - private final static Logger logger = LogManager.getLogger(DBMessage.class); - - public void log(GuildManager guildManager, MessageChannel channel, Member author, Message message) { - Session session = getSessionFactory().openSession(); - session.beginTransaction(); - - OLogMessage logMessage = new OLogMessage(); - logMessage.setGuild(guildManager.getGuildEntity()); - logMessage.setMessageId(message.getId()); - logMessage.setMessage(message.getContentRaw().trim()); - logMessage.setChannelId(channel.getId()); - logMessage.setUserId(author.getId()); - logMessage.setDateSent(message.getTimeCreated().toInstant()); - - session.persist(logMessage); - session.getTransaction().commit(); - session.close(); - } - - public void logEdit(String messageId, String newMessage) { - Session session = getSessionFactory().openSession(); - Query query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class); - query.setParameter("messageId", messageId); - - OLogMessage message = query.uniqueResult(); - if (message != null) { - session.beginTransaction(); - message.setEditedMessage(newMessage); - - session.merge(message); - session.getTransaction().commit(); - } - - session.close(); - } - - public OLogMessage getMessage(String messageId) { - Session session = getSessionFactory().openSession(); - Query query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class); - query.setParameter("messageId", messageId); - - OLogMessage message = query.uniqueResult(); - session.close(); - - return message; - } - - public List getMessagesFromUser(String userId, String guildId, int max) { - Session session = getSessionFactory().openSession(); - Query query = session.createNamedQuery("OLogMessage.findByUserIdEquals", OLogMessage.class); - query.setParameter("guildId", guildId); - query.setParameter("userId", userId); - query.setMaxResults(max); - - List messages = query.getResultList(); - session.close(); - - return messages; - } - - public String getMessageString(String messageId) { - Session session = getSessionFactory().openSession(); - Query query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class); - query.setParameter("messageId", messageId); - - OLogMessage message = query.uniqueResult(); - session.close(); - - if (message == null) - return null; - else - return message.getMessage(); - - } -} diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OBlacklist.java b/src/main/java/me/bobert/netneutrality/db/entities/OBlacklist.java index 5db94c6..6a44f51 100644 --- a/src/main/java/me/bobert/netneutrality/db/entities/OBlacklist.java +++ b/src/main/java/me/bobert/netneutrality/db/entities/OBlacklist.java @@ -10,9 +10,6 @@ import jakarta.persistence.*; @Entity @Table(name = "blacklist") -@NamedQueries({ - @NamedQuery(name = "OBlacklist.findByWordEqualsIgnoreCaseAndGuild_GuildIdEquals", query = "select o from OBlacklist o where upper(o.word) = upper(:word) and o.guild.guildId = :guildId") -}) public class OBlacklist { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OLogMessage.java b/src/main/java/me/bobert/netneutrality/db/entities/OLogMessage.java index e8b8a82..4211dc7 100644 --- a/src/main/java/me/bobert/netneutrality/db/entities/OLogMessage.java +++ b/src/main/java/me/bobert/netneutrality/db/entities/OLogMessage.java @@ -12,10 +12,6 @@ import java.time.Instant; @Entity @Table(name = "log_message") -@NamedQueries({ - @NamedQuery(name = "OLogMessage.findByMessageIdEquals", query = "select o from OLogMessage o where o.messageId = :messageId"), - @NamedQuery(name = "OLogMessage.findByGuild_GuildIdEqualsAndUserIdEquals", query = "select o from OLogMessage o where o.guild.guildId = :guildId and o.userId = :userId") -}) public class OLogMessage { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OTask.java b/src/main/java/me/bobert/netneutrality/db/entities/OTask.java new file mode 100644 index 0000000..035d3bf --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/db/entities/OTask.java @@ -0,0 +1,60 @@ +package me.bobert.netneutrality.db.entities; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import jakarta.persistence.*; + +@Entity +@Table(name = "tasks", uniqueConstraints = {@UniqueConstraint(columnNames = {"task_id"})}) +public class OTask { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Integer id; + + @Column(name = "task_id", nullable = false) + private String taskId; + + @Column(name = "task_class", nullable = false) + private String taskClass; + + @Lob + @Column(name = "payload") + private String payload; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public String getTaskClass() { + return taskClass; + } + + public void setTaskClass(String taskClass) { + this.taskClass = taskClass; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } + +} \ No newline at end of file diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OWebUser.java b/src/main/java/me/bobert/netneutrality/db/entities/OWebUser.java new file mode 100644 index 0000000..808bf7b --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/db/entities/OWebUser.java @@ -0,0 +1,86 @@ +package me.bobert.netneutrality.db.entities; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import jakarta.persistence.*; +import org.hibernate.annotations.ColumnDefault; + +import java.time.Instant; + +@Entity +@Table(name = "web_users") +public class OWebUser { + @Id + @Column(name = "id", nullable = false) + private String id; + + @Lob + @Column(name = "email", nullable = false) + private String email; + + @Column(name = "token", nullable = false) + private String token; + + @ColumnDefault("current_timestamp()") + @Column(name = "expires", nullable = false) + private Instant expires; + + @Column(name = "refresh_token", nullable = false) + private String refreshToken; + + @Column(name = "remember_token", length = 100) + private String rememberToken; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Instant getExpires() { + return expires; + } + + public void setExpires(Instant expires) { + this.expires = expires; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getRememberToken() { + return rememberToken; + } + + public void setRememberToken(String rememberToken) { + this.rememberToken = rememberToken; + } + +} \ No newline at end of file diff --git a/src/main/java/me/bobert/netneutrality/db/repository/BlacklistRepository.java b/src/main/java/me/bobert/netneutrality/db/repository/BlacklistRepository.java new file mode 100644 index 0000000..5ffc645 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/db/repository/BlacklistRepository.java @@ -0,0 +1,160 @@ +package me.bobert.netneutrality.db.repository; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.GuildManager; +import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.db.SessionManager; +import me.bobert.netneutrality.db.Repository; +import me.bobert.netneutrality.db.entities.OBlacklist; +import me.bobert.netneutrality.db.entities.OGuild; +import me.bobert.netneutrality.utils.JpaUtils; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import org.hibernate.Session; +import org.hibernate.query.Query; + +import java.util.List; + +public class BlacklistRepository implements Repository { + + @Override + public List findAll() { + List blacklists; + + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery("FROM OBlacklist", OBlacklist.class); + + blacklists = query.list(); + } + + return blacklists; + } + + @Override + public void deleteBy(String id) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); + + Query query = session.createQuery("SELECT o FROM OBlacklist o WHERE o.id = :blacklistId", OBlacklist.class); + query.setParameter("blacklistId", id); + + OBlacklist oTask = query.uniqueResult(); + if (oTask != null) { + session.remove(oTask); + } + + session.getTransaction().commit(); + } + } + + public CommandExecutionResult add(Guild guild, Member member, String word) { + GuildManager guildManager = GuildManager.getManager(guild); + + OBlacklist bl = getWordFromDb(word, guild.getId()); + if (bl == null) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); + bl = new OBlacklist(); + bl.setGuild(guildManager.getGuildEntity()); + bl.setCreatorId(member.getId()); + bl.setWord(word); + bl.setEnabled(true); + + session.persist(bl); + session.getTransaction().commit(); + } + + guildManager.reloadBlacklistCache(); + return CommandExecutionResult.success("Added the word [%s] to the blacklist.", word); + } + + return CommandExecutionResult.info("Word is already blacklisted"); + } + + public CommandExecutionResult delete(Guild guild, String word) { + GuildManager guildManager = GuildManager.getManager(guild); + + OBlacklist bl = getWordFromDb(word, guild.getId()); + if (bl != null) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); + session.remove(bl); + session.getTransaction().commit(); + } + + guildManager.reloadBlacklistCache(); + return CommandExecutionResult.success("Removed the word [%s] to the blacklist.", word); + } + + return CommandExecutionResult.info("Word is not blacklisted"); + } + + public CommandExecutionResult enable(Guild guild, String word) { + GuildManager guildManager = GuildManager.getManager(guild); + + OBlacklist bl = getWordFromDb(word, guild.getId()); + if (bl != null && !bl.getEnabled()) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); + bl.setEnabled(true); + session.merge(bl); + session.getTransaction().commit(); + } + + guildManager.reloadBlacklistCache(); + return CommandExecutionResult.success("Enabled the word [%s] for the blacklist.", word); + } + + return CommandExecutionResult.info("Either word doesn't exist or is already enabled"); + } + + public CommandExecutionResult disable(Guild guild, String word) { + GuildManager guildManager = GuildManager.getManager(guild); + + OBlacklist bl = getWordFromDb(word, guild.getId()); + if (bl != null && bl.getEnabled()) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); + bl.setEnabled(false); + session.merge(bl); + session.getTransaction().commit(); + } + + guildManager.reloadBlacklistCache(); + return CommandExecutionResult.success("Disabled the word [%s] for the blacklist.", word); + } + + return CommandExecutionResult.info("Either word doesn't exist or is already disabled"); + } + + public List getBlacklistForGuild(Guild guild) { + List blacklist; + + try(Session session = SessionManager.getSessionFactory().openSession()) { + GuildManager guildManager = GuildManager.getManager(guild); + + OGuild guildEntity = guildManager.getGuildEntity(); + JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId()); + + blacklist = guildEntity.getBlacklists().stream().toList(); + } + + return blacklist; + } + + private OBlacklist getWordFromDb(String word, String guildId) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery("SELECT o FROM OBlacklist o WHERE upper(o.word) = upper(:word) AND o.guild.guildId = :guildId", OBlacklist.class); + query.setParameter("guildId", guildId); + query.setParameter("word", word); + OBlacklist blacklist = query.uniqueResult(); + + session.close(); + return blacklist; + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/db/repository/MessageRepository.java b/src/main/java/me/bobert/netneutrality/db/repository/MessageRepository.java new file mode 100644 index 0000000..a591a32 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/db/repository/MessageRepository.java @@ -0,0 +1,121 @@ +package me.bobert.netneutrality.db.repository; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.GuildManager; +import me.bobert.netneutrality.core.db.SessionManager; +import me.bobert.netneutrality.db.Repository; +import me.bobert.netneutrality.db.entities.OLogMessage; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import org.hibernate.Session; +import org.hibernate.query.Query; + +import java.util.List; + +public class MessageRepository implements Repository { + @Override + public List findAll() { + List messages; + + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery("FROM OLogMessage", OLogMessage.class); + + messages = query.list(); + } + + return messages; + } + + @Override + public void deleteBy(String id) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); + + Query query = session.createQuery("SELECT o FROM OLogMessage o WHERE o.id = :messageId", OLogMessage.class); + query.setParameter("messageId", id); + + OLogMessage oLogMessage = query.uniqueResult(); + if (oLogMessage != null) { + session.remove(oLogMessage); + } + + session.getTransaction().commit(); + } + } + + public void log(GuildManager guildManager, MessageChannel channel, Member author, Message message) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); + + OLogMessage logMessage = new OLogMessage(); + logMessage.setGuild(guildManager.getGuildEntity()); + logMessage.setMessageId(message.getId()); + logMessage.setMessage(message.getContentRaw().trim()); + logMessage.setChannelId(channel.getId()); + logMessage.setUserId(author.getId()); + logMessage.setDateSent(message.getTimeCreated().toInstant()); + + session.persist(logMessage); + session.getTransaction().commit(); + } + } + + public void logEdit(String messageId, String newMessage) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery("SELECT o FROM OLogMessage o WHERE o.id = :messageId", OLogMessage.class); + query.setParameter("messageId", messageId); + + OLogMessage message = query.uniqueResult(); + if (message != null) { + session.beginTransaction(); + message.setEditedMessage(newMessage); + + session.merge(message); + session.getTransaction().commit(); + } + } + } + + public OLogMessage getMessage(String messageId) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery("SELECT o FROM OLogMessage o WHERE o.id = :messageId", OLogMessage.class); + query.setParameter("messageId", messageId); + + return query.uniqueResult(); + } + + } + + public List getMessagesFromUser(String userId, String guildId, int max) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery( + "SELECT o FROM OLogMessage o WHERE o.guild.guildId = :guildId AND o.userId = :userId", + OLogMessage.class + ); + query.setParameter("guildId", guildId); + query.setParameter("userId", userId); + query.setMaxResults(max); + + return query.getResultList(); + } + } + + public String getMessageString(String messageId) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery("SELECT o FROM OLogMessage o WHERE o.id = :messageId", OLogMessage.class); + query.setParameter("messageId", messageId); + + OLogMessage message = query.uniqueResult(); + if (message != null) { + return message.getMessage(); + } + + return null; + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/db/repository/TaskRepository.java b/src/main/java/me/bobert/netneutrality/db/repository/TaskRepository.java new file mode 100644 index 0000000..18dc4bf --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/db/repository/TaskRepository.java @@ -0,0 +1,60 @@ +package me.bobert.netneutrality.db.repository; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + + +import me.bobert.netneutrality.core.db.SessionManager; +import me.bobert.netneutrality.db.Repository; +import me.bobert.netneutrality.db.entities.OTask; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hibernate.Session; +import org.hibernate.query.Query; + +import java.util.List; + +public class TaskRepository implements Repository { + private static final Logger logger = LogManager.getLogger(TaskRepository.class); + + @Override + public List findAll() { + List tasks; + + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery("FROM OTask", OTask.class); + + tasks = query.list(); + } + + return tasks; + } + + public OTask findByTaskId(String id) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + Query query = session.createQuery("SELECT o FROM OTask o WHERE o.taskId = :taskId", OTask.class); + query.setParameter("taskId", id); + + return query.uniqueResult(); + } + } + + @Override + public void deleteBy(String id) { + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); + + Query query = session.createQuery("SELECT o FROM OTask o WHERE o.taskId = :taskId", OTask.class); + query.setParameter("taskId", id); + + OTask oTask = query.uniqueResult(); + if (oTask != null) { + session.remove(oTask); + } + + session.getTransaction().commit(); + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/exceptions/CommandException.java b/src/main/java/me/bobert/netneutrality/exceptions/CommandException.java deleted file mode 100644 index c006425..0000000 --- a/src/main/java/me/bobert/netneutrality/exceptions/CommandException.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.bobert.netneutrality.exceptions; - -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import me.bobert.netneutrality.core.context.CommandContext; - -public abstract class CommandException extends RuntimeException { - - public static final String DEFAULT_MESSAGE = "Unknown error during command execution."; - private final CommandContext context; - - public CommandException(String message, CommandContext context, Throwable cause) { - super(message == null ? DEFAULT_MESSAGE : message, cause); - this.context = context; - } - - public CommandContext getCommandContext() { - return context; - } -} diff --git a/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java b/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java index 0639059..3692be2 100644 --- a/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java +++ b/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java @@ -11,14 +11,9 @@ import me.bobert.netneutrality.core.context.CommandContext; /** * Logs the error internally, without sending a message */ -public class CommandExecutionException extends CommandException { +public class CommandExecutionException extends ContextException { - /** - * Logs the error internally, without sending a message - * @param context The command context - */ public CommandExecutionException(String message, CommandContext context, Throwable cause) { super(message, context, cause); } - } diff --git a/src/main/java/me/bobert/netneutrality/exceptions/ContextException.java b/src/main/java/me/bobert/netneutrality/exceptions/ContextException.java new file mode 100644 index 0000000..d8ac1f5 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/exceptions/ContextException.java @@ -0,0 +1,24 @@ +package me.bobert.netneutrality.exceptions; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.Context; + +public class ContextException extends RuntimeException { + public static final String DEFAULT_MESSAGE = "Unknown error during execution."; + private final Context context; + + public ContextException(String message, Context context, Throwable cause) { + super(message == null ? DEFAULT_MESSAGE : message, cause); + this.context = context; + } + + public Context getContext() { + return context; + } + + +} diff --git a/src/main/java/me/bobert/netneutrality/exceptions/TaskExecutionException.java b/src/main/java/me/bobert/netneutrality/exceptions/TaskExecutionException.java new file mode 100644 index 0000000..2f08e5e --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/exceptions/TaskExecutionException.java @@ -0,0 +1,15 @@ +package me.bobert.netneutrality.exceptions; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.context.TaskContext; + +public class TaskExecutionException extends ContextException { + + public TaskExecutionException(String message, TaskContext context, Throwable cause) { + super(message, context, cause); + } +} diff --git a/src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHandler.java b/src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHook.java similarity index 92% rename from src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHandler.java rename to src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHook.java index ebdb410..9151539 100644 --- a/src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHandler.java +++ b/src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHook.java @@ -12,7 +12,7 @@ import net.dv8tion.jda.api.events.guild.GuildLeaveEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; -public class ServerJoinHandler extends ListenerAdapter { +public class ServerJoinHook extends ListenerAdapter { @Override public void onGuildJoin(@NotNull GuildJoinEvent event) { diff --git a/src/main/java/me/bobert/netneutrality/hooks/interaction/ButtonInteractionHook.java b/src/main/java/me/bobert/netneutrality/hooks/interaction/ButtonInteractionHook.java new file mode 100644 index 0000000..14aa283 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/hooks/interaction/ButtonInteractionHook.java @@ -0,0 +1,22 @@ +package me.bobert.netneutrality.hooks.interaction; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.ButtonTaskContext; +import me.bobert.netneutrality.core.tasks.TaskManager; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; + +public class ButtonInteractionHook extends ListenerAdapter { + + @Override + public void onButtonInteraction(@NotNull ButtonInteractionEvent event) { + ButtonTaskContext context = new ButtonTaskContext(event); + + TaskManager.getInstance().handle(context); + } +} diff --git a/src/main/java/me/bobert/netneutrality/hooks/interaction/ModalInteractionHook.java b/src/main/java/me/bobert/netneutrality/hooks/interaction/ModalInteractionHook.java new file mode 100644 index 0000000..203af42 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/hooks/interaction/ModalInteractionHook.java @@ -0,0 +1,22 @@ +package me.bobert.netneutrality.hooks.interaction; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.ModalTaskContext; +import me.bobert.netneutrality.core.tasks.TaskManager; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; + +public class ModalInteractionHook extends ListenerAdapter { + + @Override + public void onModalInteraction(@NotNull ModalInteractionEvent event) { + ModalTaskContext context = new ModalTaskContext(event); + + TaskManager.getInstance().handle(context); + } +} diff --git a/src/main/java/me/bobert/netneutrality/hooks/SlashCommandHooks.java b/src/main/java/me/bobert/netneutrality/hooks/interaction/SlashCommandHook.java similarity index 54% rename from src/main/java/me/bobert/netneutrality/hooks/SlashCommandHooks.java rename to src/main/java/me/bobert/netneutrality/hooks/interaction/SlashCommandHook.java index 1cf33e0..c0ebf8e 100644 --- a/src/main/java/me/bobert/netneutrality/hooks/SlashCommandHooks.java +++ b/src/main/java/me/bobert/netneutrality/hooks/interaction/SlashCommandHook.java @@ -1,25 +1,22 @@ -package me.bobert.netneutrality.hooks; +package me.bobert.netneutrality.hooks.interaction; /* * Copyright (c) anthorob * * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.CommandProcessor; -import me.bobert.netneutrality.core.context.SlashCommandContext; +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandProcessor; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; -public class SlashCommandHooks extends ListenerAdapter { +public class SlashCommandHook extends ListenerAdapter { @Override public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) { - String commandName = event.getName(); - - SlashCommandContext commandContext = SlashCommandContext.createContext(event); - CommandProcessor processor = new CommandProcessor(commandName, commandContext); + SlashCommandContext commandContext = new SlashCommandContext(event); + CommandProcessor processor = new CommandProcessor(commandContext); processor.dispatch(); - } } diff --git a/src/main/java/me/bobert/netneutrality/hooks/message/EditHandler.java b/src/main/java/me/bobert/netneutrality/hooks/message/EditHandler.java index 3fab697..688f5f2 100644 --- a/src/main/java/me/bobert/netneutrality/hooks/message/EditHandler.java +++ b/src/main/java/me/bobert/netneutrality/hooks/message/EditHandler.java @@ -7,10 +7,9 @@ package me.bobert.netneutrality.hooks.message; */ import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.core.SessionManager; -import me.bobert.netneutrality.db.DBController; -import me.bobert.netneutrality.db.controllers.DBMessage; +import me.bobert.netneutrality.core.db.SessionManager; import me.bobert.netneutrality.db.entities.OGuild; +import me.bobert.netneutrality.db.repository.MessageRepository; import me.bobert.netneutrality.utils.JpaUtils; import me.bobert.netneutrality.utils.MessageFunction; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; @@ -46,8 +45,9 @@ public class EditHandler extends ListenerAdapter { TextChannel channel = event.getGuild().getTextChannelById(guildEntity.getLogChannel()); if (channel != null) { - String originalMessage = DBController.get(DBMessage.class).getMessageString(event.getMessage().getId()); - DBController.get(DBMessage.class).logEdit(event.getMessage().getId(), event.getMessage().getContentDisplay()); + MessageRepository repository = new MessageRepository(); + String originalMessage = repository.getMessageString(event.getMessage().getId()); + repository.logEdit(event.getMessage().getId(), event.getMessage().getContentDisplay()); channel.sendMessageEmbeds(MessageFunction.buildEditMessage(event.getMember(), event.getMessage(), originalMessage)).queue(); } diff --git a/src/main/java/me/bobert/netneutrality/hooks/message/EmojiReactionHook.java b/src/main/java/me/bobert/netneutrality/hooks/message/EmojiReactionHook.java new file mode 100644 index 0000000..7fb8483 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/hooks/message/EmojiReactionHook.java @@ -0,0 +1,22 @@ +package me.bobert.netneutrality.hooks.message; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.EmojiTaskContext; +import me.bobert.netneutrality.core.tasks.TaskManager; +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; + +public class EmojiReactionHook extends ListenerAdapter { + + @Override + public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) { + EmojiTaskContext context = new EmojiTaskContext(event); + TaskManager.getInstance().handle(context); + } + +} diff --git a/src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHandler.java b/src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHook.java similarity index 52% rename from src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHandler.java rename to src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHook.java index 7defada..2e623a5 100644 --- a/src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHandler.java +++ b/src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHook.java @@ -5,16 +5,14 @@ package me.bobert.netneutrality.hooks.message; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.CommandProcessor; -import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.core.context.MessageCommandContext; +import me.bobert.netneutrality.context.MessageCommandContext; +import me.bobert.netneutrality.core.command.CommandProcessor; import me.bobert.netneutrality.utils.Settings; -import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; -public class MessageReceivedHandler extends ListenerAdapter { +public class MessageReceivedHook extends ListenerAdapter { @Override public void onMessageReceived(@NotNull MessageReceivedEvent event) { @@ -24,19 +22,12 @@ public class MessageReceivedHandler extends ListenerAdapter { if (!event.isFromGuild()) return; - Message message = event.getMessage(); - - MessageCommandContext context = MessageCommandContext.createContext(event); - - String rawMessage = message.getContentDisplay().trim(); + String rawMessage = event.getMessage().getContentDisplay().trim(); String prefix = Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$"); if (rawMessage.startsWith(prefix)) { - rawMessage = rawMessage.replaceFirst(String.format("^[%s\\s]{1,}", prefix), "").trim(); - // rawMessage = rawMessage.substring(prefix.length()).trim(); - String commandString = rawMessage.split(" ", 2)[0]; - - CommandProcessor processor = new CommandProcessor(commandString, context); + MessageCommandContext context = new MessageCommandContext(event); + CommandProcessor processor = new CommandProcessor(context); processor.dispatch(); } } diff --git a/src/main/java/me/bobert/netneutrality/hooks/message/ReactionHandler.java b/src/main/java/me/bobert/netneutrality/hooks/message/ReactionHandler.java deleted file mode 100644 index bcbae8c..0000000 --- a/src/main/java/me/bobert/netneutrality/hooks/message/ReactionHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.bobert.netneutrality.hooks.message; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.events.message.MessageUpdateEvent; -import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; -import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; -import org.jetbrains.annotations.NotNull; - -public class ReactionHandler extends ListenerAdapter { - - @Override - public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) { - Member member = event.getMember(); - } - - @Override - public void onMessageReactionRemove(@NotNull MessageReactionRemoveEvent event) { - - } - - @Override - public void onMessageUpdate(@NotNull MessageUpdateEvent event) { - - } -} diff --git a/src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHandler.java b/src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHook.java similarity index 87% rename from src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHandler.java rename to src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHook.java index 00543ca..0733a38 100644 --- a/src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHandler.java +++ b/src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHook.java @@ -9,7 +9,7 @@ import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; -public class VoiceEventHandler extends ListenerAdapter { +public class VoiceEventHook extends ListenerAdapter { @Override public void onGuildVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) { diff --git a/src/main/java/me/bobert/netneutrality/http/WebModule.java b/src/main/java/me/bobert/netneutrality/http/WebModule.java index 03a2750..04ed06d 100644 --- a/src/main/java/me/bobert/netneutrality/http/WebModule.java +++ b/src/main/java/me/bobert/netneutrality/http/WebModule.java @@ -18,16 +18,20 @@ import java.io.IOException; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.TimeUnit; public class WebModule { private final String url; - private final OkHttpClient client = new OkHttpClient(); + private final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .writeTimeout(5, TimeUnit.SECONDS) + .readTimeout(5, TimeUnit.SECONDS) + .build(); private Response response; //private final String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 OPR/82.0.4227.43"; private static final String userAgent = String.format("Net Neutrality/%s (Discord bot, tony@bobert.me)", NetNeutrality.VERSION); - public WebModule(String url) { this.url = url; } diff --git a/src/main/java/me/bobert/netneutrality/http/server/WebApi.java b/src/main/java/me/bobert/netneutrality/http/server/WebApi.java index 216c7c1..c7a595d 100644 --- a/src/main/java/me/bobert/netneutrality/http/server/WebApi.java +++ b/src/main/java/me/bobert/netneutrality/http/server/WebApi.java @@ -7,7 +7,7 @@ package me.bobert.netneutrality.http.server; */ import com.sun.net.httpserver.HttpServer; -import me.bobert.netneutrality.thread.BaseThreadFactory; +import me.bobert.netneutrality.core.thread.BaseThreadFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -18,7 +18,7 @@ import java.util.concurrent.Executors; public class WebApi { - private final static Logger logger = LogManager.getLogger(WebApi.class); + private static final Logger logger = LogManager.getLogger(WebApi.class); private HttpServer server; diff --git a/src/main/java/me/bobert/netneutrality/music/MusicManager.java b/src/main/java/me/bobert/netneutrality/music/MusicManager.java index 4b69cbc..59fce8a 100644 --- a/src/main/java/me/bobert/netneutrality/music/MusicManager.java +++ b/src/main/java/me/bobert/netneutrality/music/MusicManager.java @@ -13,19 +13,18 @@ import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import dev.lavalink.youtube.YoutubeAudioSourceManager; -import me.bobert.netneutrality.command.CommandExecutionResult; import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.core.LogHandler; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.thread.ThreadManager; import me.bobert.netneutrality.db.GSettings; -import me.bobert.netneutrality.exceptions.CommandExecutionException; import me.bobert.netneutrality.music.source.UdioAudioSourceManager; import me.bobert.netneutrality.thread.MusicPlayerAutoDisconnectThread; -import me.bobert.netneutrality.thread.ThreadManager; +import me.bobert.netneutrality.utils.MusicUtils; +import me.bobert.netneutrality.utils.Settings; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -37,16 +36,16 @@ import java.util.concurrent.TimeUnit; public class MusicManager { - private final static Logger logger = LogManager.getLogger(MusicManager.class); - private final static Map managers = new ConcurrentHashMap<>(); - private final static ScheduledExecutorService executor = ThreadManager.getMusicExecutor(); + private static final Logger logger = LogManager.getLogger(MusicManager.class); + private static final Map managers = new ConcurrentHashMap<>(); + private static final ScheduledExecutorService executor = ThreadManager.getScheduledExecutor(); - private final static AudioPlayerManager playerManager = new DefaultAudioPlayerManager(); + private static final AudioPlayerManager playerManager = new DefaultAudioPlayerManager(); private final Guild guild; private final AudioPlayer player; private final TrackScheduler scheduler; - private ScheduledFuture autoDisconnectThread; private final int baseVolume; + private ScheduledFuture autoDisconnectThread; private MusicManager(Guild guild) { this.guild = guild; @@ -73,16 +72,20 @@ public class MusicManager { playerManager.registerSourceManager(SoundCloudAudioSourceManager.createDefault()); playerManager.registerSourceManager(new UdioAudioSourceManager()); playerManager.registerSourceManager(new HttpAudioSourceManager()); + + String po = Settings.getInstance().get("NETNEUTRALITY_PO_TOKEN"); + String visitor = Settings.getInstance().get("NETNEUTRALITY_VISITOR_DATA"); + dev.lavalink.youtube.clients.Web.setPoTokenAndVisitorData(po, visitor); } - public boolean isNotConnectedTo(VoiceChannel vChannel) { + public boolean isNotConnectedTo(AudioChannel vChannel) { return vChannel == null || !vChannel.equals(guild.getAudioManager().getConnectedChannel()); } - public synchronized void connect(VoiceChannel vChannel) { - if (vChannel != null && isNotConnectedTo(vChannel)) { - logger.debug("Guild {}: connecting to voice channel {}", vChannel.getGuild().getId(), vChannel.getId()); - guild.getAudioManager().openAudioConnection(vChannel); + public synchronized void connect(AudioChannel audioChannel) { + if (audioChannel != null && isNotConnectedTo(audioChannel)) { + logger.debug("Guild {}: connecting to voice channel {}", audioChannel.getGuild().getId(), audioChannel.getId()); + guild.getAudioManager().openAudioConnection(audioChannel); guild.getAudioManager().setSelfDeafened(true); player.setVolume(baseVolume); @@ -144,42 +147,22 @@ public class MusicManager { } } - public synchronized void play(String query, CommandContext context, boolean needToBeConnected, boolean silent) { + public synchronized void play(String query, CommandContext context) { + play(query, context, false); + } + + public synchronized void play(String query, CommandContext context, boolean silent) { logger.debug("Guild {}: request to play '{}' by user {}", guild.getId(), query, context.getGuildUser().getId()); - Member member = context.getGuildUser(); - MessageChannel channel = context.getChannel(); - - if (member.getVoiceState() == null) { - logger.error("Guild {}: Voice state is null", guild.getId()); - context.replyToMessage("Voice state is null, something has gone terribly wrong."); - LogHandler.log(new CommandExecutionException("Voice state is null", context, null)); - - return; - } - - if (!member.getVoiceState().inAudioChannel()) { - context.replyToMessage("You are not in a voice channel"); - return; - } - - VoiceChannel vChannel = (VoiceChannel) member.getVoiceState().getChannel(); - if (needToBeConnected && isNotConnectedTo(vChannel)) { - context.replyToMessage("The player is not in your voice channel"); - return; - } - - if (isNotConnectedTo(vChannel)) { - connect(vChannel); - scheduler.attachTextChannel(channel); - } - SourceFinder.AudioSourceType type = SourceFinder.getSource(query); String identifier = SourceFinder.getIdentifier(query); - - if (identifier != null) { + if (identifier == null) { + context.reply("No source found for provided query"); + return; + } + + if (MusicUtils.connect(context)) { TrackLoadingHandler loadingHandler = new TrackLoadingHandler(context, this, identifier, type, silent); - playerManager.loadItemOrdered(this, identifier, loadingHandler); } } @@ -189,29 +172,28 @@ public class MusicManager { } public synchronized CommandExecutionResult voteSkip(CommandContext context) { - VoiceChannel voiceChannel = (VoiceChannel) guild.getAudioManager().getConnectedChannel(); + AudioChannel audioChannel = guild.getAudioManager().getConnectedChannel(); - if (voiceChannel == null) { - return CommandExecutionResult.none(); + if (audioChannel == null) { + return CommandExecutionResult.warn("Bot is not connected to a voice channel"); } if (!isCurrentlyPlaying()) { - return CommandExecutionResult.none(); + return CommandExecutionResult.warn("No tracks are currently playing"); } QueueManager.TrackInfo info = scheduler.getQueueManager().peekHeadOfQueue(); if (info == null) { - return CommandExecutionResult.none(); + return CommandExecutionResult.warn("Queue is empty"); } // Minus the bot itself - int connected = voiceChannel.getMembers().size() - 1; + int connected = audioChannel.getMembers().size() - 1; if (connected < 3) { - context.reply("Skipping current track since not enough people are connected to the voice channel."); scheduler.getQueueManager().skip(); - return CommandExecutionResult.none(); + return CommandExecutionResult.info("Skipping current track since not enough people are connected to the voice channel."); } double needed = connected / 2.1; @@ -221,11 +203,12 @@ public class MusicManager { if (info.getSkips().size() > needed) { return scheduler.getQueueManager().skip(); - } else { - context.reply("Adding your vote to skip the current track. Current votes: %d/%d", info.getSkips().size(), (int) Math.ceil(needed)); } - return CommandExecutionResult.none(); + return CommandExecutionResult.success(String.format("Adding your vote to skip the current track. Current votes: %d/%d", + info.getSkips().size(), + (int) Math.ceil(needed)) + ); } public synchronized void setVolume(int volume) { diff --git a/src/main/java/me/bobert/netneutrality/music/QueueManager.java b/src/main/java/me/bobert/netneutrality/music/QueueManager.java index 9676928..c81207d 100644 --- a/src/main/java/me/bobert/netneutrality/music/QueueManager.java +++ b/src/main/java/me/bobert/netneutrality/music/QueueManager.java @@ -7,7 +7,7 @@ package me.bobert.netneutrality.music; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import me.bobert.netneutrality.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.CommandExecutionResult; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import org.apache.logging.log4j.LogManager; @@ -24,7 +24,7 @@ public class QueueManager { private final AudioTrack track; private final Member member; private final Set skips; - private final Logger logger = LogManager.getLogger(QueueManager.class); + private static final Logger logger = LogManager.getLogger(QueueManager.class); public TrackInfo(AudioTrack track, Member member) { this.track = track; @@ -186,6 +186,9 @@ public class QueueManager { public void queue(AudioTrack track, Member member) { TrackInfo info = new TrackInfo(track, member); + if (queue.contains(info)) { + return; + } synchronized (queue) { if (queue.contains(info)) { diff --git a/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java b/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java index 007a040..f96a1fa 100644 --- a/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java +++ b/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java @@ -12,13 +12,15 @@ import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import me.bobert.netneutrality.core.LogHandler; import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.tasks.TaskManager; +import me.bobert.netneutrality.tasks.music.LoadPlaylistTask; import me.bobert.netneutrality.utils.MessageFunction; import net.dv8tion.jda.api.entities.Guild; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class TrackLoadingHandler implements AudioLoadResultHandler { - private final Logger logger = LogManager.getLogger(TrackLoadingHandler.class); + private static final Logger logger = LogManager.getLogger(TrackLoadingHandler.class); private final String identifier; private final SourceFinder.AudioSourceType type; @@ -44,7 +46,7 @@ public class TrackLoadingHandler implements AudioLoadResultHandler { scheduler.getQueueManager().queue(audioTrack, context.getGuildUser()); if (!silent) { - context.reply(MessageFunction.buildAddingTrackMessage(audioTrack, type)); + context.reply(MessageFunction.buildAddingTrackMessage(audioTrack, context.getGuildUser(), type)); } } @@ -52,33 +54,13 @@ public class TrackLoadingHandler implements AudioLoadResultHandler { public void playlistLoaded(AudioPlaylist playlist) { logger.debug("Guild {}: playlist loaded: {}", guild.getId(), identifier); - if (!playlist.getTracks().isEmpty()) { - if (playlist.isSearchResult()) { - - AudioTrack track = playlist.getTracks().get(0); - scheduler.getQueueManager().queue(track, context.getGuildUser()); - - if (!silent) { - context.reply(MessageFunction.buildAddingTrackMessage(track, type)); - } - - } else { - context.deleteMessage(); - if (!silent) { - context.reply(MessageFunction.buildAddingPlaylistMessage(playlist, identifier, type)); - } - - for (AudioTrack track : playlist.getTracks()) { - scheduler.getQueueManager().queue(track, context.getGuildUser()); - } - } - } + TaskManager.getInstance().insert(context, new LoadPlaylistTask(playlist)); } @Override public void noMatches() { if (!silent) - context.replyToMessage("No matches found"); + context.reply("No matches found"); } @Override @@ -87,6 +69,6 @@ public class TrackLoadingHandler implements AudioLoadResultHandler { LogHandler.log(e); if (!silent) - context.replyToMessage(MessageFunction.buildSentryErrorMessage(new RuntimeException("Failed to load track: " + e.getMessage()))); + context.reply(MessageFunction.buildSentryErrorMessage(new RuntimeException("Failed to load track: " + e.getMessage(), e))); } } diff --git a/src/main/java/me/bobert/netneutrality/music/TrackScheduler.java b/src/main/java/me/bobert/netneutrality/music/TrackScheduler.java index 99de059..4294809 100644 --- a/src/main/java/me/bobert/netneutrality/music/TrackScheduler.java +++ b/src/main/java/me/bobert/netneutrality/music/TrackScheduler.java @@ -10,22 +10,24 @@ import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; +import me.bobert.netneutrality.context.MusicChannelContext; import me.bobert.netneutrality.core.LogHandler; import me.bobert.netneutrality.utils.MessageFunction; import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class TrackScheduler extends AudioEventAdapter { - private final static Logger logger = LogManager.getLogger(TrackScheduler.class); + private static final Logger logger = LogManager.getLogger(TrackScheduler.class); private final QueueManager queueManager; private final MusicManager manager; private final AudioPlayer player; - private MessageChannel currentChannel; private final Guild guild; + private MusicChannelContext context; + private AudioTrack lastStuckTrack = null; public TrackScheduler(MusicManager manager) { this.manager = manager; @@ -50,28 +52,18 @@ public class TrackScheduler extends AudioEventAdapter { return guild; } - public MessageChannel getTextChannel() { - return currentChannel; + public MusicChannelContext getCurrentContext() { + return context; } - public synchronized void attachTextChannel(MessageChannel channel) { - this.currentChannel = channel; + public synchronized void attachChannel(MessageChannel channel) { + if (channel instanceof GuildMessageChannel guildMessageChannel) { + this.context = new MusicChannelContext(guildMessageChannel); + } } public synchronized void detachTextChannel() { - this.currentChannel = null; - } - - public void sendMessageToChannel(String msg) { - if (currentChannel != null && currentChannel.canTalk()) { - currentChannel.sendMessage(msg).queue(); - } - } - - public void sendMessageToChannel(MessageEmbed embed, MessageEmbed... embeds) { - if (currentChannel != null && currentChannel.canTalk()) { - currentChannel.sendMessageEmbeds(embed, embeds).queue(); - } + this.context = null; } @Override @@ -118,13 +110,58 @@ public class TrackScheduler extends AudioEventAdapter { public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) { logger.debug("Guild {}: exception while playing track [{}]: {}", guild.getId(), track.getIdentifier(), exception.getMessage()); LogHandler.log(exception); - sendMessageToChannel(MessageFunction.buildSentryErrorMessage(new RuntimeException(String.format("Error while playing track [%s]: %s", track.getInfo().title, exception.getMessage())))); + + context.reply( + MessageFunction.buildSentryErrorMessage( + new RuntimeException(String.format("Error while playing track: %s", track.getInfo().title), exception) + ) + ); } @Override public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) { - logger.debug("Guild {}: currently playing track is stuck: {}", guild.getId(), track.getIdentifier()); - sendMessageToChannel("Current track is stuck, skipping to next"); - queueManager.startNextTrack(); + logger.error("Guild {}: currently playing track is stuck: {}", guild.getId(), track.getIdentifier()); + + if (lastStuckTrack == null || !lastStuckTrack.getIdentifier().equals(track.getIdentifier())) { + lastStuckTrack = track; + + logger.debug("Trying to load stuck track again: {}", track.getIdentifier()); + context.async("Current track is stuck, trying to load it again", asyncMessage -> { + try { + QueueManager.TrackInfo info = queueManager.peekHeadOfQueue(); + if (info != null && info.getTrack().getIdentifier().equals(track.getIdentifier())) { + AudioTrack clone = track.makeClone(); + long lastPosition = track.getPosition(); + + player.stopTrack(); + + if (queueManager.peekHeadOfQueue() != null && queueManager.peekHeadOfQueue().getTrack().getIdentifier().equals(track.getIdentifier())) { + queueManager.removeHeadOfQueue(); + } + + queueManager.queue(clone, info.getRequester()); + + AudioTrack playing = player.getPlayingTrack(); + if (playing != null && playing.getIdentifier().equals(track.getIdentifier())) { + playing.setPosition(lastPosition); + lastStuckTrack = null; + } else { + asyncMessage.edit("Failed to seek track to last timestamp, the track will play from the beginning"); + } + } else { + asyncMessage.edit("Failed to load last playing track, skipping to next.."); + queueManager.startNextTrack(); + } + } catch (Exception e) { + LogHandler.log(e); + asyncMessage.edit("Failed to load last playing track, skipping to next.."); + queueManager.startNextTrack(); + } + }); + } else { + context.reply("Current track is stuck, skipping to next.."); + queueManager.startNextTrack(); + } + } } diff --git a/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java b/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java index eaeaa79..202978b 100644 --- a/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java +++ b/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java @@ -129,7 +129,7 @@ public class UdioAudioSourceManager implements AudioSourceManager { } protected JsonBrowser loadTrackInfoFromPageContent(String content) throws IOException { - String startTag = "self.__next_f.push([1,\"9:"; + String startTag = "self.__next_f.push([1,\"d:"; String endTag = "\\n\"])"; String json = DataFormatTools.extractBetween(content, startTag, endTag); diff --git a/src/main/java/me/bobert/netneutrality/tasks/ButtonTask.java b/src/main/java/me/bobert/netneutrality/tasks/ButtonTask.java new file mode 100644 index 0000000..64132eb --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/ButtonTask.java @@ -0,0 +1,43 @@ +package me.bobert.netneutrality.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.ButtonTaskContext; +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.AbstractTask; + +import java.time.OffsetDateTime; +import java.util.UUID; + +public abstract class ButtonTask extends AbstractTask { + private final UUID id = UUID.randomUUID(); + + @Override + public String getId() { + return id.toString(); + } + + @Override + public void execute(TaskContext context) { + if (context instanceof ButtonTaskContext) { + execute((ButtonTaskContext) context); + } else { + throw new IllegalArgumentException("Invalid context for this task"); + } + } + + public abstract void execute(ButtonTaskContext context); + + @Override + public boolean oneshot() { + return true; + } + + @Override + public boolean isExpired() { + return getCreationTime().plusMinutes(15).isBefore(OffsetDateTime.now()); + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/EmojiTask.java b/src/main/java/me/bobert/netneutrality/tasks/EmojiTask.java new file mode 100644 index 0000000..a953d24 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/EmojiTask.java @@ -0,0 +1,44 @@ +package me.bobert.netneutrality.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.EmojiTaskContext; +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.AbstractPersistableTask; + +public abstract class EmojiTask extends AbstractPersistableTask { + private final String guildId; + private final String messageId; + + public EmojiTask(String guildId, String messageId) { + this.guildId = guildId; + this.messageId = messageId; + } + + public String getGuildId() { + return guildId; + } + + public String getMessageId() { + return messageId; + } + + @Override + public String getId() { + return String.format("%s:%s", getGuildId(), getMessageId()); + } + + @Override + public void execute(TaskContext context) { + if (context instanceof EmojiTaskContext) { + execute((EmojiTaskContext) context); + } else { + throw new IllegalArgumentException("Invalid context for this task"); + } + } + + public abstract void execute(EmojiTaskContext context); +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/ModalTask.java b/src/main/java/me/bobert/netneutrality/tasks/ModalTask.java new file mode 100644 index 0000000..6b6813b --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/ModalTask.java @@ -0,0 +1,43 @@ +package me.bobert.netneutrality.tasks; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.ModalTaskContext; +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.core.tasks.AbstractTask; + +import java.time.OffsetDateTime; +import java.util.UUID; + +public abstract class ModalTask extends AbstractTask { + private final UUID id = UUID.randomUUID(); + + @Override + public String getId() { + return id.toString(); + } + + @Override + public void execute(TaskContext context) { + if (context instanceof ModalTaskContext) { + execute((ModalTaskContext) context); + } else { + throw new IllegalArgumentException("Invalid context for this task"); + } + } + + public abstract void execute(ModalTaskContext context); + + @Override + public boolean oneshot() { + return true; + } + + @Override + public boolean isExpired() { + return getCreationTime().plusMinutes(5).isBefore(OffsetDateTime.now()); + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/emoji/EmojiReactionRoleTask.java b/src/main/java/me/bobert/netneutrality/tasks/emoji/EmojiReactionRoleTask.java new file mode 100644 index 0000000..0fa2f6f --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/emoji/EmojiReactionRoleTask.java @@ -0,0 +1,55 @@ +package me.bobert.netneutrality.tasks.emoji; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.EmojiTaskContext; +import me.bobert.netneutrality.tasks.EmojiTask; +import net.dv8tion.jda.api.entities.Role; + +import java.util.HashMap; +import java.util.Map; + +public class EmojiReactionRoleTask extends EmojiTask { + private final Map emojiMapping; + + public EmojiReactionRoleTask(String guildId, String messageId) { + super(guildId, messageId); + this.emojiMapping = new HashMap<>(); + } + + public EmojiReactionRoleTask(String guildId, String messageId, Map emojiMapping) { + super(guildId, messageId); + this.emojiMapping = emojiMapping; + } + + public void addEmoji(String emoji, String role) { + emojiMapping.put(emoji, role); + } + + public void removeEmoji(String emoji) { + emojiMapping.remove(emoji); + } + + public Map getEmojiMapping() { + return emojiMapping; + } + + @Override + public String getName() { + return "emoji-reaction-role"; + } + + @Override + public void execute(EmojiTaskContext context) { + if (getEmojiMapping().containsKey(context.getEmoji())) { + Role role = context.getGuild().getRoleById(getEmojiMapping().get(context.getEmoji())); + + if (role != null) { + context.getGuildManager().addRolesToMember(context.getGuildUser(), role); + } + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/git/ReportIssueTask.java b/src/main/java/me/bobert/netneutrality/tasks/git/ReportIssueTask.java new file mode 100644 index 0000000..fd147bf --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/git/ReportIssueTask.java @@ -0,0 +1,97 @@ +package me.bobert.netneutrality.tasks.git; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import io.sentry.Sentry; +import me.bobert.netneutrality.context.tasks.ModalTaskContext; +import me.bobert.netneutrality.core.LogHandler; +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.tasks.ModalTask; +import me.bobert.netneutrality.utils.Settings; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; +import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.gitlab4j.api.GitLabApi; +import org.gitlab4j.api.GitLabApiException; +import org.gitlab4j.api.models.Issue; + +public class ReportIssueTask extends ModalTask { + private static final Logger logger = LogManager.getLogger(ReportIssueTask.class); + + @Override + public String getName() { + return "report-issue"; + } + + @Override + public void replyToInteraction(CommandContext context) { + TextInput title = TextInput.create("title", "Title", TextInputStyle.SHORT) + .setPlaceholder("Brief description of the issue.") + .setMinLength(10) + .setMaxLength(100) + .build(); + + TextInput body = TextInput.create("description", "Description", TextInputStyle.PARAGRAPH) + .setPlaceholder("And I scream from the top of my lungs What's going on?") + .setMinLength(30) + .setMaxLength(1000) + .build(); + + Modal modal = Modal.create(getId(), "Report a bug") + .addComponents(ActionRow.of(title), ActionRow.of(body)) + .build(); + + context.reply(modal); + } + + @Override + public void execute(ModalTaskContext context) { + context.ephemeral(); + + String gitlabHost = Settings.getInstance().get("GITLAB_HOST"); + String gitlabToken = Settings.getInstance().get("GITLAB_TOKEN"); + String projectId = Settings.getInstance().get("GITLAB_PROJECT_ID"); + + if (gitlabHost.isBlank() || gitlabToken.isBlank() || projectId.isBlank()) { + logger.error("Gitlab is not configured in the bot settings, can't execute task"); + context.reply("Command is not configured, failed to create issue."); + return; + } + + ModalMapping modalTitle = context.getEvent().getValue("title"); + ModalMapping modalDescription = context.getEvent().getValue("description"); + + if (modalTitle == null || modalDescription == null) { + context.reply("Failed to create issue."); + throw new IllegalArgumentException("Modal values are null"); + } + + User user = context.getDiscordUser(); + try (GitLabApi gitLabApi = new GitLabApi(gitlabHost, gitlabToken)) { + gitLabApi.setRequestTimeout(1000, 5000); + + String title = String.format("Bug report: %s", modalTitle.getAsString()); + StringBuilder description = new StringBuilder(); + description.append("Issue reported from user of bot\n\n"); + description.append("User: ").append(user.getName()).append(String.format("[%s]", user.getId())).append("\n\n"); + description.append("Last Sentry ID: ").append(Sentry.getLastEventId()).append("\n\n"); + description.append("Description from user: \n\n").append(modalDescription.getAsString()); + + Issue issue = gitLabApi.getIssuesApi().createIssue(projectId, title, description.toString()); + + context.reply(String.format("Issue was successfully created: %s", issue.getWebUrl())); + + } catch (GitLabApiException e) { + context.reply("Failed to create issue."); + LogHandler.log(e); + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/music/AddAllTracksTask.java b/src/main/java/me/bobert/netneutrality/tasks/music/AddAllTracksTask.java new file mode 100644 index 0000000..f74d948 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/music/AddAllTracksTask.java @@ -0,0 +1,66 @@ +package me.bobert.netneutrality.tasks.music; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import me.bobert.netneutrality.context.tasks.ButtonTaskContext; +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.tasks.trait.WithParent; +import me.bobert.netneutrality.music.MusicManager; +import me.bobert.netneutrality.music.SourceFinder; +import me.bobert.netneutrality.music.TrackScheduler; +import me.bobert.netneutrality.tasks.ButtonTask; +import me.bobert.netneutrality.utils.MessageFunction; +import me.bobert.netneutrality.utils.MusicUtils; +import net.dv8tion.jda.api.entities.Member; + +import java.util.List; + +public class AddAllTracksTask extends ButtonTask implements WithParent { + private final LoadPlaylistTask parentTask; + List tracks; + + public AddAllTracksTask(LoadPlaylistTask parentTask, List tracks) { + this.parentTask = parentTask; + this.tracks = tracks; + } + + @Override + public String getName() { + return "add-all-tracks"; + } + + @Override + public void replyToInteraction(CommandContext context) { + + } + + @Override + public void execute(ButtonTaskContext context) { + MusicManager manager = MusicManager.get(context.getGuild()); + TrackScheduler scheduler = manager.getScheduler(); + + Member member = context.getGuildUser(); + + if (MusicUtils.connect(context)) { + for (AudioTrack track : tracks) { + scheduler.getQueueManager().queue(track, member); + } + + context.reply(MessageFunction.buildAddingPlaylistMessage(tracks, parentTask.getPlaylist().getName(), member, SourceFinder.AudioSourceType.Youtube)); + } + } + + @Override + public LoadPlaylistTask getParentTask() { + return parentTask; + } + + @Override + public boolean shouldKeepChilds() { + return false; + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/music/CancelLoadTask.java b/src/main/java/me/bobert/netneutrality/tasks/music/CancelLoadTask.java new file mode 100644 index 0000000..f07602b --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/music/CancelLoadTask.java @@ -0,0 +1,45 @@ +package me.bobert.netneutrality.tasks.music; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.context.tasks.ButtonTaskContext; +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable; +import me.bobert.netneutrality.core.tasks.trait.WithParent; +import me.bobert.netneutrality.tasks.ButtonTask; + +public class CancelLoadTask extends ButtonTask implements WithParent, NotAcknowledgeable { + private final LoadPlaylistTask parentTask; + + public CancelLoadTask(LoadPlaylistTask parentTask) { + this.parentTask = parentTask; + } + + @Override + public String getName() { + return "cancel-playlist-loading"; + } + + @Override + public void replyToInteraction(CommandContext context) { + + } + + @Override + public LoadPlaylistTask getParentTask() { + return parentTask; + } + + @Override + public boolean shouldKeepChilds() { + return false; + } + + @Override + public void execute(ButtonTaskContext context) { + // Interaction will get removed after execution + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/music/LoadPlaylistTask.java b/src/main/java/me/bobert/netneutrality/tasks/music/LoadPlaylistTask.java new file mode 100644 index 0000000..88927ac --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/music/LoadPlaylistTask.java @@ -0,0 +1,133 @@ +package me.bobert.netneutrality.tasks.music; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import me.bobert.netneutrality.context.tasks.ButtonTaskContext; +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.tasks.Task; +import me.bobert.netneutrality.core.tasks.trait.Deletable; +import me.bobert.netneutrality.core.tasks.trait.WithChildren; +import me.bobert.netneutrality.db.GSettings; +import me.bobert.netneutrality.music.MusicManager; +import me.bobert.netneutrality.tasks.ButtonTask; +import me.bobert.netneutrality.utils.MessageFunction; +import me.bobert.netneutrality.utils.MusicUtils; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.ItemComponent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; + +import java.util.ArrayList; +import java.util.List; + +import static me.bobert.netneutrality.music.SourceFinder.AudioSourceType.Youtube; + +public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deletable { + private final List childrens = new ArrayList<>(); + private final AudioPlaylist playlist; + + public LoadPlaylistTask(AudioPlaylist playlist) { + this.playlist = playlist; + } + + public AudioPlaylist getPlaylist() { + return playlist; + } + + @Override + public String getName() { + return "load-playlist"; + } + + @Override + public boolean isInsertable() { + return false; + } + + @Override + public void replyToInteraction(CommandContext context) { + if (playlist.getTracks().isEmpty()) { + context.reply("No tracks found"); + return; + } + + int searchResultsLimit = Integer.parseInt(context.getGuildManager() + .getGuildConfigManager() + .getSetting(GSettings.MUSIC_SEARCHRESULTS_LIMIT)); + int maxPlaylistSizeBeforeAsking = Integer.parseInt(context.getGuildManager() + .getGuildConfigManager() + .getSetting(GSettings.MUSIC_MAXPLAYLISTSIZE)); + + // Search results + if (playlist.isSearchResult()) { + List searchResults = playlist.getTracks().stream().limit(searchResultsLimit).toList(); + List buttonList = new ArrayList<>(); + + for (int i = 0; i < searchResults.size(); i++) { + AudioTrack track = searchResults.get(i); + Task buttonTask = new LoadTrackFromPlaylistTask(this, track); + + Button button = Button.primary(buttonTask.getId(), String.valueOf(i + 1)); + buttonList.add(button); + childrens.add(buttonTask); + } + + ActionRow firstRow = ActionRow.of(buttonList); + ActionRow secondRow = commonButtons(searchResults, "Add all tracks", "Not for me honey"); + + context.edit(MessageFunction.buildSearchResultsMessage(searchResults, context.getGuildUser()), firstRow, secondRow); + + return; + } + + List tracks = playlist.getTracks(); + if (tracks.size() > maxPlaylistSizeBeforeAsking) { + ActionRow firstRow = commonButtons(playlist.getTracks(), "I'm ready", "Please don't"); + + context.edit(MessageFunction.buildPlaylistLoadMessage(playlist, context.getGuildUser()), firstRow); + return; + } + + // Load playlist normally + MusicManager manager = MusicManager.get(context.getGuild()); + if (MusicUtils.connect(context)) { + for (AudioTrack track : tracks) { + manager.getScheduler().getQueueManager().queue(track, context.getGuildUser()); + } + + context.edit(MessageFunction.buildAddingPlaylistMessage(tracks, playlist.getName(), context.getGuildUser(), Youtube)); + } + } + + @Override + public void execute(ButtonTaskContext context) { + // Only the buttons will get executed, this task is only used to insert them + } + + private ActionRow commonButtons(List tracks, String addAllText, String cancelText) { + Task addAllTracksTask = new AddAllTracksTask(this, tracks); + Button addAllButton = Button.success(addAllTracksTask.getId(), addAllText).withEmoji(Emoji.fromFormatted("U+1F60E")); + childrens.add(addAllTracksTask); + + Task cancelTask = new CancelLoadTask(this); + Button cancelButton = Button.danger(cancelTask.getId(), cancelText).withEmoji(Emoji.fromFormatted("U+1F92E")); + childrens.add(cancelTask); + + return ActionRow.of(addAllButton, cancelButton); + } + + @Override + public List getChildTasks() { + return childrens; + } + + @Override + public void onInteractionDelete() { + removeAllChildTasks(); + } +} diff --git a/src/main/java/me/bobert/netneutrality/tasks/music/LoadTrackFromPlaylistTask.java b/src/main/java/me/bobert/netneutrality/tasks/music/LoadTrackFromPlaylistTask.java new file mode 100644 index 0000000..3e9b2a1 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/tasks/music/LoadTrackFromPlaylistTask.java @@ -0,0 +1,62 @@ +package me.bobert.netneutrality.tasks.music; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import me.bobert.netneutrality.context.tasks.ButtonTaskContext; +import me.bobert.netneutrality.core.context.CommandContext; +import me.bobert.netneutrality.core.tasks.trait.WithParent; +import me.bobert.netneutrality.music.MusicManager; +import me.bobert.netneutrality.music.SourceFinder; +import me.bobert.netneutrality.music.TrackScheduler; +import me.bobert.netneutrality.tasks.ButtonTask; +import me.bobert.netneutrality.utils.MessageFunction; +import me.bobert.netneutrality.utils.MusicUtils; +import net.dv8tion.jda.api.entities.Member; + +public class LoadTrackFromPlaylistTask extends ButtonTask implements WithParent { + private final LoadPlaylistTask parentTask; + private final AudioTrack track; + + public LoadTrackFromPlaylistTask(LoadPlaylistTask parentTask, AudioTrack track) { + this.parentTask = parentTask; + this.track = track; + } + + @Override + public String getName() { + return String.format("load-track-%s", track.getIdentifier()); + } + + @Override + public void replyToInteraction(CommandContext context) { + + } + + @Override + public void execute(ButtonTaskContext context) { + MusicManager manager = MusicManager.get(context.getGuild()); + TrackScheduler scheduler = manager.getScheduler(); + + Member member = context.getGuildUser(); + + if (MusicUtils.connect(context)) { + scheduler.getQueueManager().queue(track, member); + + context.reply(MessageFunction.buildAddingTrackMessage(track, member, SourceFinder.AudioSourceType.Youtube)); + } + } + + @Override + public LoadPlaylistTask getParentTask() { + return parentTask; + } + + @Override + public boolean shouldKeepChilds() { + return false; + } +} diff --git a/src/main/java/me/bobert/netneutrality/thread/CleanupTaskThread.java b/src/main/java/me/bobert/netneutrality/thread/CleanupTaskThread.java new file mode 100644 index 0000000..94ec25c --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/thread/CleanupTaskThread.java @@ -0,0 +1,24 @@ +package me.bobert.netneutrality.thread;/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.core.tasks.Task; +import me.bobert.netneutrality.core.tasks.TaskManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CleanupTaskThread implements Runnable { + private static final Logger logger = LogManager.getLogger(CleanupTaskThread.class); + + @Override + public void run() { + logger.info("Cleaning up tasks.."); + + TaskManager.getInstance().getTasks() + .stream() + .filter(Task::isExpired) + .forEach(task -> TaskManager.getInstance().removeTask(task)); + } +} diff --git a/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java b/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java index e97e091..747c8e2 100644 --- a/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java +++ b/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java @@ -9,7 +9,7 @@ import me.bobert.netneutrality.NetNeutrality; import me.bobert.netneutrality.music.MusicManager; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -17,7 +17,7 @@ import java.util.List; public class MusicPlayerAutoDisconnectThread implements Runnable { - private final Logger logger = LogManager.getLogger(MusicPlayerAutoDisconnectThread.class); + private static final Logger logger = LogManager.getLogger(MusicPlayerAutoDisconnectThread.class); private final MusicManager manager; //TODO: move to scheduler @@ -28,7 +28,7 @@ public class MusicPlayerAutoDisconnectThread implements Runnable { @Override public void run() { Guild guild = manager.getGuild(); - VoiceChannel channel = (VoiceChannel) guild.getAudioManager().getConnectedChannel(); + AudioChannel channel = guild.getAudioManager().getConnectedChannel(); try { if (channel != null) { @@ -47,7 +47,7 @@ public class MusicPlayerAutoDisconnectThread implements Runnable { } } - private boolean conditionsMetToDisconnect(VoiceChannel channel) { + private boolean conditionsMetToDisconnect(AudioChannel channel) { List members = channel.getMembers().stream().filter(member -> !member.getId().equals(NetNeutrality.getSelf().getId())).toList(); return members.isEmpty() || manager.getScheduler().getQueueManager().isEmpty(); diff --git a/src/main/java/me/bobert/netneutrality/utils/ChannelMessagePurger.java b/src/main/java/me/bobert/netneutrality/utils/ChannelMessagePurger.java index 95ae067..876f649 100644 --- a/src/main/java/me/bobert/netneutrality/utils/ChannelMessagePurger.java +++ b/src/main/java/me/bobert/netneutrality/utils/ChannelMessagePurger.java @@ -5,16 +5,13 @@ package me.bobert.netneutrality.utils; * https://discord.gg/qUa6zNFZsd */ -import io.sentry.ISpan; -import io.sentry.ITransaction; -import io.sentry.Sentry; -import io.sentry.SpanStatus; import me.bobert.netneutrality.core.context.CommandContext; -import me.bobert.netneutrality.thread.ThreadManager; +import me.bobert.netneutrality.core.thread.ThreadManager; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageHistory; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageEditData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,6 +21,7 @@ import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +//TODO: not useful since we can't delete messages that are more than 2 weeks old public class ChannelMessagePurger { private static final Logger logger = LogManager.getLogger(ChannelMessagePurger.class); @@ -67,21 +65,6 @@ public class ChannelMessagePurger { }); } - public void purgeChannel(CommandContext context) { - ThreadManager.getExecutor().submit(() -> { - if (lock.tryLock()) { - try { - int total = countAllChannelMessages(channel); - context.reply("Number of messages to purge: %d", total); - - safePurge(context, total); - } finally { - lock.unlock(); - } - } - }); - } - private synchronized int countAllChannelMessages(GuildMessageChannel channel) { logger.info("Guild {}: counting all messages from channel {}", channel.getGuild().getId(), channel.getId()); MessageHistory history = channel.getHistory(); @@ -100,9 +83,7 @@ public class ChannelMessagePurger { } private synchronized void safePurge(CommandContext context, int toPurge) { - ITransaction transaction = Sentry.startTransaction(String.format("purge messages from guild %s)", channel.getGuild().getId()), "task"); - transaction.setData("channel", channel.getId()); - transaction.setData("toPurge", toPurge); + logger.info("Safe purging {} messages from channel {}", toPurge, channel.getId()); if (toPurge == 1) { toPurge += 1; @@ -111,60 +92,48 @@ public class ChannelMessagePurger { int tracker = toPurge; int iteration = tracker / 100; boolean olderThan2Weeks = false; - context.replyToMessage("Purging iterations to do: %d", iteration); + context.edit("Purging iterations to do: %d", iteration); try { for (int i = 0; i < iteration; i++) { - ISpan innerSpan = transaction.startChild("retrieve-messages", "Processing iteration " + (i + 1) + "/" + iteration); + logger.debug("Processing iteration {}/{}", (i + 1), iteration); try { List messages = channel.getHistory().retrievePast(100).complete(); channel.deleteMessages(messages).complete(); - tracker -= 100; - innerSpan.setStatus(SpanStatus.OK); } catch (IllegalArgumentException ex) { olderThan2Weeks = true; } catch (Exception e) { - innerSpan.setThrowable(e); - innerSpan.setStatus(SpanStatus.INTERNAL_ERROR); + logger.error("Error purging iteration {}/{}", (i + 1), iteration, e); } finally { - innerSpan.finish(); + tracker -= 100; } } if (tracker > 0) { - ISpan innerSpan = transaction.startChild("retrieve-messages", String.format("Processing final iteration (%d messages)", tracker)); + logger.debug("Processing final iteration ({} messages)", tracker); try { List messages = channel.getHistory().retrievePast(tracker).complete(); channel.deleteMessages(messages).complete(); - innerSpan.setStatus(SpanStatus.OK); } catch (IllegalArgumentException ex) { olderThan2Weeks = true; } catch (Exception e) { - innerSpan.setThrowable(e); - innerSpan.setStatus(SpanStatus.INTERNAL_ERROR); - } finally { - innerSpan.finish(); + logger.error("Error purging final iteration", e); } - - - innerSpan.finish(); } } catch (Exception e) { - transaction.setThrowable(e); - transaction.setStatus(SpanStatus.INTERNAL_ERROR); - } finally { - transaction.finish(); - MessageCreateBuilder builder = new MessageCreateBuilder() - .addContent("Finished purging ") - .addContent(String.valueOf(toPurge)) - .addContent(" messages."); - - if (olderThan2Weeks) { - builder.addContent("\n").addContent("Messages older than two weeks weren't deleted."); - } - context.reply(builder.build()); - lock.unlock(); + logger.error("Error purging messages", e); } + + MessageCreateBuilder builder = new MessageCreateBuilder() + .addContent("Finished purging ") + .addContent(String.valueOf(toPurge)) + .addContent(" messages."); + + if (olderThan2Weeks) { + builder.addContent("\n").addContent("Messages older than two weeks weren't deleted."); + } + context.edit(MessageEditData.fromCreateData(builder.build())); + lock.unlock(); } } diff --git a/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java b/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java index 2996950..c3be9bb 100644 --- a/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java +++ b/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java @@ -6,12 +6,11 @@ package me.bobert.netneutrality.utils; * https://discord.gg/qUa6zNFZsd */ -import io.sentry.Breadcrumb; -import io.sentry.Sentry; -import io.sentry.SentryLevel; import me.bobert.netneutrality.core.GuildManager; -import me.bobert.netneutrality.core.SessionManager; +import me.bobert.netneutrality.core.db.SessionManager; import me.bobert.netneutrality.db.entities.OGuild; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.hibernate.HibernateException; import org.hibernate.ObjectNotFoundException; import org.hibernate.Session; @@ -21,6 +20,8 @@ import java.util.function.Consumer; public class JpaUtils { + private static final Logger log = LogManager.getLogger(JpaUtils.class); + /** * Tries to read the persistent state into the object. *

@@ -38,16 +39,14 @@ public class JpaUtils { session.load(object, id); } catch (HibernateException exception) { if (exception instanceof ObjectNotFoundException) { - Sentry.addBreadcrumb(Breadcrumb.error("ObjectNotFoundException")); - if (object instanceof OGuild oGuild) { String cachedId = oGuild.getGuildId(); - Sentry.captureMessage("Trying to load Guild[" + cachedId + "] while entity doesn't exist in database, reloading..", SentryLevel.ERROR); + log.error("Trying to load Guild[{}] while entity doesn't exist in database, reloading..", cachedId); GuildManager guildManager = GuildManager.getManager(cachedId); guildManager.reloadEntity(); guildManager.getBlacklistCache().clear(); - Sentry.captureMessage("Guild[" + cachedId + "] reloaded", SentryLevel.INFO); + log.info("Guild[{}] reloaded", cachedId); } } @@ -57,14 +56,14 @@ public class JpaUtils { } public static void updateWithTransaction(Object persistantEntity, Runnable runnable) { - Session session = SessionManager.getSessionFactory().openSession(); - session.beginTransaction(); + try(Session session = SessionManager.getSessionFactory().openSession()) { + session.beginTransaction(); - runnable.run(); + runnable.run(); - session.merge(persistantEntity); - session.getTransaction().commit(); - session.close(); + session.merge(persistantEntity); + session.getTransaction().commit(); + } } public static void getWithSession(OGuild oGuild, Consumer consumer) { @@ -72,15 +71,16 @@ public class JpaUtils { } /** + * Loads the entity and relations from the database and executes the given consumer + * * @param persistantEntity The entity to load * @param id The identifier for the entity * @param consumer The comsumer, which exposes the session */ public static void getWithSession(Object persistantEntity, Serializable id, Consumer consumer) { - Session session = SessionManager.getSessionFactory().openSession(); - tryLoad(session, persistantEntity, id); - - consumer.accept(session); - session.close(); + try(Session session = SessionManager.getSessionFactory().openSession()) { + tryLoad(session, persistantEntity, id); + consumer.accept(session); + } } } diff --git a/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java b/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java index 3cd26b6..4cf05ad 100644 --- a/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java +++ b/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java @@ -10,11 +10,13 @@ import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; import io.sentry.Sentry; -import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.command.CommandManager; +import me.bobert.netneutrality.command.Command; import me.bobert.netneutrality.command.CommandType; +import me.bobert.netneutrality.core.command.CommandManager; import me.bobert.netneutrality.core.context.CommandContext; -import me.bobert.netneutrality.exceptions.CommandException; +import me.bobert.netneutrality.core.context.Context; +import me.bobert.netneutrality.core.context.TaskContext; +import me.bobert.netneutrality.exceptions.ContextException; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.music.QueueManager; import me.bobert.netneutrality.music.SourceFinder; @@ -22,6 +24,7 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import org.apache.commons.lang3.time.DurationFormatUtils; import org.apache.logging.log4j.LogManager; @@ -36,7 +39,7 @@ import java.util.Set; public class MessageFunction { - private final static Logger logger = LogManager.getLogger(MessageFunction.class); + private static final Logger logger = LogManager.getLogger(MessageFunction.class); public static MessageEmbed buildCurrentlyPlayingMessage(MusicManager manager) { AudioTrack current = manager.getCurrentlyPlaying(); @@ -53,7 +56,7 @@ public class MessageFunction { return message.build(); } - public static MessageEmbed buildAddingTrackMessage(AudioTrack track, SourceFinder.AudioSourceType type) { + public static MessageEmbed buildAddingTrackMessage(AudioTrack track, Member member, SourceFinder.AudioSourceType type) { EmbedBuilder message = new EmbedBuilder(); String url = track.getIdentifier(); String thumbnail = null; @@ -74,37 +77,24 @@ public class MessageFunction { message.setColor(Color.BLUE); message.addField("Track title: ", String.format("[**`%s`**](%s)", track.getInfo().title, url), false); message.addField("Duration: ", DurationFormatUtils.formatDuration(track.getDuration(), "**HH:mm:ss**", true), false); + message.setFooter(member.getEffectiveName(), member.getEffectiveAvatarUrl()); message.setTimestamp(OffsetDateTime.now()); return message.build(); } - public static MessageEmbed buildSearchResultsMessage(List tracks) { + public static MessageEmbed buildAddingPlaylistMessage(List tracks, String playlistName, Member member, SourceFinder.AudioSourceType type) { EmbedBuilder message = new EmbedBuilder(); - - message.setTitle("Select the track to add to the queue"); - message.setColor(Color.BLUE); - - int i = 0; - for (AudioTrack track : tracks) { - message.addField(++i + ": ", String.format("**`%s`**", track.getInfo().title), false); - } - - return message.build(); - } - - public static MessageEmbed buildAddingPlaylistMessage(AudioPlaylist playlist, String identifier, SourceFinder.AudioSourceType type) { - EmbedBuilder message = new EmbedBuilder(); - String url = identifier; + String url = ""; String thumbnail = null; if (type == SourceFinder.AudioSourceType.Youtube || type == SourceFinder.AudioSourceType.YTSearch) { - url = String.format("https://youtu.be/%s", playlist.getTracks().get(0).getIdentifier()); - thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", playlist.getTracks().get(0).getIdentifier()); + url = String.format("https://youtu.be/%s", tracks.get(0).getIdentifier()); + thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", tracks.get(0).getIdentifier()); } if (type == SourceFinder.AudioSourceType.YTMusic) { - thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", playlist.getTracks().get(0).getIdentifier()); + thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", tracks.get(0).getIdentifier()); } if (thumbnail != null) { @@ -112,8 +102,9 @@ public class MessageFunction { } message.setTitle("Adding playlist to queue.."); message.setColor(Color.BLUE); - message.addField("Playlist: ", String.format("[**`%s`**](%s)", playlist.getName(), url), false); - message.addField("Number of tracks: ", String.valueOf(playlist.getTracks().size()), false); + message.addField("Playlist: ", String.format("[**`%s`**](%s)", playlistName, url), false); + message.addField("Number of tracks: ", String.valueOf(tracks.size()), false); + message.setFooter(member.getEffectiveName(), member.getEffectiveAvatarUrl()); message.setTimestamp(OffsetDateTime.now()); return message.build(); @@ -174,19 +165,47 @@ public class MessageFunction { return message.build(); } - public static ArrayList buildHelpMessage(Member member) { + public static MessageEmbed buildSearchResultsMessage(List tracks, Member member) { + EmbedBuilder embed = new EmbedBuilder(); + + embed.setTitle("Search results"); + embed.setDescription("I found the following tracks for your query, please select which one you want to play:"); + embed.setColor(Color.MAGENTA); + + int i = 0; + for (AudioTrack track : tracks) { + embed.addField(++i + ": ", track.getInfo().title, false); + } + + embed.setFooter(member.getEffectiveName(), member.getEffectiveAvatarUrl()); + embed.setTimestamp(OffsetDateTime.now()); + + return embed.build(); + } + + public static MessageEmbed buildPlaylistLoadMessage(AudioPlaylist playlist, Member member) { + EmbedBuilder embed = new EmbedBuilder(); + + embed.setTitle(String.format("Loading playlist: %s", playlist.getName())); + embed.setDescription(String.format("You are about to load %d tracks from this playlist, are you sure?", playlist.getTracks().size())); + embed.setColor(Color.CYAN); + + embed.setFooter(member.getEffectiveName(), member.getEffectiveAvatarUrl()); + embed.setTimestamp(OffsetDateTime.now()); + + return embed.build(); + } + + public static ArrayList buildHelpMessage() { HashMap help = new HashMap<>(); - for (AbstractCommand command : CommandManager.commands.values()) { + for (Command command : CommandManager.commands.values()) { if (command.getType() == null) continue; String description = "No description yet"; if (command.isEnabled() && command.getType() != CommandType.UNLISTED) { - if (command.getType() == CommandType.ADMINISTRATION && !UserFunction.isAdmin(member)) - continue; - if (command.getDescription() != null) description = command.getDescription(); @@ -223,23 +242,33 @@ public class MessageFunction { return message.build(); } - public static MessageEmbed buildDebugMessage(CommandContext context, CommandException error) { + public static MessageEmbed buildDebugMessage(ContextException exception) { + Context context = exception.getContext(); + EmbedBuilder message = new EmbedBuilder(); message.setColor(Color.RED); - message.setTitle(":warning: Exception in command execution"); - message.addField("Caused by command:", context.getCommand().getCommandName(), false); - message.addField("Exception:", error.getMessage(), false); - if (error.getCause() != null) { + message.setTitle(":warning: Exception in context"); + + if (context instanceof CommandContext commandContext) { + message.addField("Caused by command:", commandContext.getCommand().getCommandName(), false); + } + + if (context instanceof TaskContext taskContext) { + message.addField("Caused by task with ID:", taskContext.getId(), false); + } + + message.addField("Exception:", exception.getMessage(), false); + if (exception.getCause() != null) { String cause; - if (error.getCause().getMessage() != null && !error.getCause().getMessage().isEmpty()) { - cause = error.getCause().getMessage(); + if (exception.getCause().getMessage() != null && !exception.getCause().getMessage().isEmpty()) { + cause = exception.getCause().getMessage(); } else { - cause = error.getCause().getClass().getName(); + cause = exception.getCause().getClass().getName(); } message.addField("Caused by:", cause, false); } - message.setFooter("Requested by " + context.getGuildUser().getEffectiveName(), context.getGuildUser().getAvatarUrl()); + message.setFooter("User in context: " + context.getDiscordUser().getEffectiveName(), context.getDiscordUser().getAvatarUrl()); message.setTimestamp(OffsetDateTime.now()); return message.build(); diff --git a/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java b/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java index 47a36e7..58c49b4 100644 --- a/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java +++ b/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java @@ -5,20 +5,50 @@ package me.bobert.netneutrality.utils; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.core.context.CommandContext; -import me.bobert.netneutrality.exceptions.CommandExecutionException; +import me.bobert.netneutrality.core.context.Context; +import me.bobert.netneutrality.exceptions.ContextException; import me.bobert.netneutrality.music.MusicManager; +import net.dv8tion.jda.api.entities.GuildVoiceState; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; public class MusicUtils { - public static boolean checkIfConnected(MusicManager manager, CommandContext context) { - if (context.getGuildUser().getVoiceState() == null) { - throw new CommandExecutionException("Voice state is null, this should not happen", context, null); + public static boolean connect(Context context) { + return connect(context, false); + } + + public static boolean connect(Context context, boolean skipChecks) { + MusicManager manager = MusicManager.get(context.getGuild()); + Member member = context.getGuildUser(); + if (member == null) { + return false; } - if (!context.getGuildUser().getVoiceState().inAudioChannel()) { + AudioChannel audioChannel = getVoiceChannel(context); + if (audioChannel == null) { + context.reply("You are not in a voice channel"); + return false; + } + + if (!skipChecks && !isUserDj(member) && (manager.isConnected() && manager.isNotConnectedTo(audioChannel))) { + context.reply("The bot isn't connected to your voice channel"); + return false; + } + + if (manager.isNotConnectedTo(audioChannel)) { + manager.connect(audioChannel); + manager.getScheduler().attachChannel(context.getChannel()); + } + + return true; + } + + public static boolean checkIfConnected(Context context) { + MusicManager manager = MusicManager.get(context.getGuild()); + + AudioChannel voiceChannel = getVoiceChannel(context); + if (voiceChannel == null) { context.reply("You are not in a voice channel"); return false; } @@ -28,8 +58,7 @@ public class MusicUtils { return false; } - VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel(); - if (manager.isNotConnectedTo(vChannel)) { + if (manager.isNotConnectedTo(voiceChannel)) { context.reply("The bot isn't in your voice channel"); return false; } @@ -37,6 +66,37 @@ public class MusicUtils { return true; } + public static boolean checkIfNotConnected(Context context) { + MusicManager manager = MusicManager.get(context.getGuild()); + + AudioChannel voiceChannel = getVoiceChannel(context); + if (voiceChannel == null) { + context.reply("You are not in a voice channel"); + return false; + } + + if (manager.isConnected()) { + context.reply("The bot is already in a voice channel"); + return false; + } + + return manager.isNotConnectedTo(voiceChannel); + } + + public static AudioChannel getVoiceChannel(Context context) { + GuildVoiceState memberVoiceState = context.getGuildUser().getVoiceState(); + + if (memberVoiceState == null) { + throw new ContextException("Voice state is null", context, null); + } + + if (!memberVoiceState.inAudioChannel()) { + return null; + } + + return memberVoiceState.getChannel(); + } + public static boolean isUserDj(Member member) { MusicManager manager = MusicManager.get(member.getGuild()); diff --git a/src/main/java/me/bobert/netneutrality/utils/Settings.java b/src/main/java/me/bobert/netneutrality/utils/Settings.java index e1084da..62d0348 100644 --- a/src/main/java/me/bobert/netneutrality/utils/Settings.java +++ b/src/main/java/me/bobert/netneutrality/utils/Settings.java @@ -77,7 +77,7 @@ public class Settings { try { Properties p = new Properties(); - InputStream is = getClass().getResourceAsStream("/app.properties"); + InputStream is = getClass().getResourceAsStream("/application.properties"); if (is != null) { p.load(is); version = p.getProperty("app.version", ""); diff --git a/src/main/resources/app.properties b/src/main/resources/application.properties similarity index 100% rename from src/main/resources/app.properties rename to src/main/resources/application.properties diff --git a/src/main/resources/hibernate.cfg.xml b/src/main/resources/hibernate.cfg.xml index 817a6be..955a01e 100644 --- a/src/main/resources/hibernate.cfg.xml +++ b/src/main/resources/hibernate.cfg.xml @@ -8,12 +8,13 @@ com.mysql.cj.jdbc.Driver org.hibernate.hikaricp.internal.HikariCPConnectionProvider - + - + + \ No newline at end of file diff --git a/src/main/resources/persistable-tasks.json b/src/main/resources/persistable-tasks.json new file mode 100644 index 0000000..7db2d5a --- /dev/null +++ b/src/main/resources/persistable-tasks.json @@ -0,0 +1,17 @@ +{ + "tasks": [ + { + "class": "me.bobert.netneutrality.tasks.emoji.EmojiReactionRoleTask", + "params": [ + "guildId", + "messageId", + "emojiMapping" + ], + "types": [ + "string", + "string", + "map" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/java/netneutrality/music/source/UdioAudioSourceManagerTest.java b/src/test/java/me/bobert/netneutrality/music/source/UdioAudioSourceManagerTest.java similarity index 86% rename from src/test/java/netneutrality/music/source/UdioAudioSourceManagerTest.java rename to src/test/java/me/bobert/netneutrality/music/source/UdioAudioSourceManagerTest.java index 745e5e2..55ec954 100644 --- a/src/test/java/netneutrality/music/source/UdioAudioSourceManagerTest.java +++ b/src/test/java/me/bobert/netneutrality/music/source/UdioAudioSourceManagerTest.java @@ -1,4 +1,4 @@ -package netneutrality.music.source; +package me.bobert.netneutrality.music.source; /* * Copyright (c) anthorob * @@ -8,8 +8,6 @@ package netneutrality.music.source; import com.sedmelluq.discord.lavaplayer.track.AudioItem; import com.sedmelluq.discord.lavaplayer.track.AudioReference; import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; -import me.bobert.netneutrality.music.source.UdioAudioSourceManager; -import me.bobert.netneutrality.music.source.UdioAudioTrack; import org.junit.Test; import static org.junit.Assert.assertNotNull; diff --git a/src/test/java/netneutrality/utils/SettingsTest.java b/src/test/java/me/bobert/netneutrality/utils/SettingsTest.java similarity index 58% rename from src/test/java/netneutrality/utils/SettingsTest.java rename to src/test/java/me/bobert/netneutrality/utils/SettingsTest.java index 3e13580..e2a23ad 100644 --- a/src/test/java/netneutrality/utils/SettingsTest.java +++ b/src/test/java/me/bobert/netneutrality/utils/SettingsTest.java @@ -1,4 +1,4 @@ -package netneutrality.utils; +package me.bobert.netneutrality.utils; /* * Copyright (c) anthorob @@ -6,7 +6,6 @@ package netneutrality.utils; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.utils.Settings; import org.junit.Test; import static org.junit.Assert.*; @@ -19,11 +18,4 @@ public class SettingsTest { assertNotNull(version); } - - @Test - public void getGitVersion() { - String version = Settings.getInstance().getGitVersion(); - - assertNotNull(version); - } } \ No newline at end of file