diff --git a/.circleci/config.yml b/.circleci/config.yml index 61c86a0..61341e8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,12 +4,7 @@ jobs: build: docker: # specify the version you desire here - - image: circleci/openjdk:8u171-jdk - - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/postgres:9.4 + - image: cimg/openjdk:20.0.1 working_directory: ~/repo @@ -19,6 +14,7 @@ jobs: steps: - checkout + - run: java --version # Download and cache dependencies - restore_cache: diff --git a/.github/ISSUE_TEMPLATE/---.md b/.github/ISSUE_TEMPLATE/---.md index cabf517..c4691cc 100644 --- a/.github/ISSUE_TEMPLATE/---.md +++ b/.github/ISSUE_TEMPLATE/---.md @@ -1,5 +1,7 @@ --- -name: その他 about: その他のIssueを立てたい場合はこちら title: '' +name: その他 +about: その他のIssueを立てたい場合はこちら +title: '' labels: '' assignees: '' diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 84cca76..d67b1f4 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -1,5 +1,7 @@ --- -name: バグレポート about: バグや予期しない動作を報告する title: "[バグレポート] タイトルを入力" +name: バグレポート +about: バグや予期しない動作を報告する +title: "[バグレポート] タイトルを入力" labels: '' assignees: '' diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index ed44b4e..b9149dd 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,5 +1,7 @@ --- -name: 機能リクエスト about: このプロジェクトにアイデアを提案 title: "[機能要求]タイトルを入力" +name: 機能リクエスト +about: このプロジェクトにアイデアを提案 +title: "[機能要求]タイトルを入力" labels: '' assignees: '' diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 4512633..22c2852 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -4,9 +4,7 @@ on: pull_request: push: branches: - - master - - develop - - 'releases/*' + - '*' jobs: qodana: diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index ccec7b6..1bec35e 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -3,13 +3,6 @@ - - - - diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index a55e7a1..6e6eec1 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,6 @@ + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 0f63fb0..d0b40cc 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -6,7 +6,6 @@ - diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index b2bb500..4ffb1c8 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,13 +1,16 @@ - + sqlite.xerial true org.sqlite.JDBC - jdbc:sqlite:C:\Users\kosug\IdeaProjects\TextToSpeak\src\main\resources\UserData.sqlite + jdbc:sqlite:C:\Users\kosug\IdeaProjects\TextToSpeakBot\src\main\resources\UserData.sqlite $ProjectFileDir$ + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.39.2/sqlite-jdbc-3.39.2.jar + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.34.0/sqlite-jdbc-3.34.0.jar diff --git a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89.xml b/.idea/dataSources/648c6ef7-2bba-4e14-b21f-f6619a5657a5.xml similarity index 54% rename from .idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89.xml rename to .idea/dataSources/648c6ef7-2bba-4e14-b21f-f6619a5657a5.xml index 88350eb..c4f4622 100644 --- a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89.xml +++ b/.idea/dataSources/648c6ef7-2bba-4e14-b21f-f6619a5657a5.xml @@ -1,827 +1,825 @@ - - 3.40.1 - - + + - - - - - - - + + + + + + + - - + + - - + + 1 + 1 - + 1 + 1 - + 1 + 1 - + 1 + 1 - + 1 + 1 - + 1 + 1 - + 1 + 1 - + 1 + 1 - + 1 + 1 - + 1 - - - - window - - 1 - + 1 + 1 - + 1 + 1 - - + 1 1 - - - + 1 1 - + 1 + 1 - + 1 + 1 - - - - window - - - window - - - - - + 1 1 - + 1 1 - + 1 + 1 - - window + + 1 - - - 1 + + 1 - - window + + 1 - - 1 + + 1 - - 1 + 1 - - - - - - 1 + + window - + window - + + + + 1 - - 1 + + window - + 1 - + + window + + 1 - + + 1 - + 1 - + 1 - 1 - + 1 - + + 1 - 1 - + + 1 - window - + 1 + + window - + 1 - 1 - + 1 - 1 - - 1 + + window - - 1 + + window - + 1 - 1 - + 1 - 1 - + 1 - 1 - + 1 - + 1 - + 1 - 1 - - - window - + window - - - window - - - window - - - window + + 1 - - + + 1 - + 1 - + 1 - + + window + + 1 - + 1 - - + 1 - + 1 - + 1 - + + 1 + window - - aggregate + + window - + 1 - - - - + 1 - 1 - - window - - aggregate + + 1 - + 1 - 1 - - window + + 1 - + 1 - - aggregate + + 1 - - window + + + 1 - + window - - 1 - - - - - - - + window - + 1 - + 1 - + 1 - + 1 - 1 - - + 1 - + + 1 - - - - window + + 1 - - 1 + + 1 - + 1 - - - - + 1 - + window - - 1 - - - - + 1 - + + + + 1 - + 1 - + 1 - - 1 + + 1 - - - + 1 - - - + 1 - - - aggregate + + + window - - + + 1 + + 1 - 1 - + 1 - + + window + + 1 - + 1 - + + + window - + 1 - + 1 - + 1 - 1 - + window - - - 1 + + window + + + window - + 1 - + 1 - + 1 - + + window + + 1 - 1 - + 1 - + 1 - + aggregate - + aggregate - - 1 + + aggregate + + + + aggregate + + + + + + + + aggregate + + + + + + + + + + + aggregate + + + + + + + + + + 1 R - - 1 + + R - - 2 + + R - + R - - 1 + + R - - 2 + + R - + R - - 1 + + R - + R - - 1 + + R - + R - - 1 + + R - + R - - 1 + + R - + R - - 1 + + R - + R - - 1 + + R - + R - - 1 + + R - - 2 + + R - + R - - 1 + + R - + R - + + R + + 1 - + 2 - + + 3 + + R - + 1 - - R + + 2 + + + 3 - + R - + 1 - + + 2 + + R - + 1 - - R + + 2 - - R + + 3 - + R - + 1 - + 2 - + R - + 1 - + 2 - - 3 - - - R - - + R - + 1 - + + 2 + + R - + 1 - + + 2 + + R - + 1 - + R - + 1 - + + 2 + + R - + 1 - + R - - R + + 1 - + R - - R + + 1 - + R - - R + + 1 + + + 2 - + R - + 1 - + + 2 + + R - + R - + 1 - + 2 - + R - + 1 - + R - + 1 - + + 2 + + R - + 1 - + R - + + 1 + + R - + 1 - + R - + 1 - + 2 - + R - + 1 - + R - + R - + 1 - - 2 - - + R - + 1 - - 2 - - + R - + 1 - + R - + + 1 + + R - + 1 - + 2 - + R - + 1 - + 2 - - 3 - - + R - + 1 - + 2 - + R - + 1 - + R - + R - + 1 - + 2 - + R - - R - - + 1 - + R - + 1 - - 2 - - - R - - - R - - + R - + 1 - - 2 - - + R - + 1 - - R - - - R - - - R + + 2 + 3 + + R - + 1 - - 2 - - R + 2 - 1 + 3 R - - R + + 1 - 1 + R - 2 + 1 R @@ -829,14 +827,14 @@ 1 - - 2 + + R - - 3 + + 1 - R + 2 R @@ -850,560 +848,560 @@ 1 - - 2 + + R + 1 + + R - + 1 - + 2 - - 3 - - + R - + 1 - + 2 - + R - + 1 - + R - + 1 - - 2 + + R - - 3 + + 1 - + R - + 1 - + 2 - - R - - - 1 + + 3 - + R - + 1 - + R - + 1 - - 2 - - + R - - 1 - - - 2 + + R - + R - + 1 - + R - + 1 - + R - + 1 - + R - + 1 - + R - + 1 - + 2 - + R - + + R + + 1 - + 2 - + R - + 1 - + R - + 1 - - 2 + + R + + + 1 - + R - + + 1 + + R - + 1 - + R - + 1 - + R - + R - - 1 + + R - + R - + 1 - - 2 - - + R - + 1 - + R - + 1 - + 2 - + R - + 1 - + R - + 1 - + + R + + + 1 + + 2 - + + 3 + + R - - 1 + + R - + R - + 1 - + R - + 1 - + 2 - + + R + + + 1 + + R - + + R + + 1 - + 2 - + R - + 1 - + 2 - + R - + R - + R - + 1 - + 2 - + R - + 1 - + 2 - + R - - R - - + 1 - + R - + 1 - - R - - - 1 + + 2 - - R + + 3 - + R - + 1 - + R - - R + + 1 - + R - + 1 - + 2 - - 3 - - + R - + 1 - - 2 - - + R - + 1 - + R - + 1 - + 2 - + R - + 1 - - 2 - - + R - + R - + + 1 + + R - + 1 - + R - + 1 - - 2 - - + R - + 1 - - 2 - - + R - + 1 - + R - + 1 - + + 2 + + R - + 1 - - R + + 2 - + R - + 1 - + R - + 1 - + + 2 + + R - + 1 - + 2 - + R - - R + + 1 - + R - + 1 - + + 2 + + R - + 1 - + + 2 + + R - + 1 - + R - + 1 - + 2 - + R - + + 1 + + R - + 1 - + 2 - - 3 - - + R - + 1 - + 2 - - 3 - - + R - + 1 - + R - - 1 - - + R - + 1 - + R - + 1 - - R - - + R - + 1 - + R - + + 1 + + R - + 1 - + 2 - + R - + 1 - + R - + 1 - + R - + 1 - - R - - R + 2 - - 1 + + R - R + 1 - 1 + 2 R @@ -1423,5 +1421,70 @@ 1 + +
+
+ 1 +
+ + integer|0s + 1 + + + text|0s + 2 + + + 3 + + + integer|0s + 1 + 1 + + + TEXT|0s + 2 + + + real|0s + 3 + + + real|0s + 4 + + + real|0s + 5 + + + real|0s + 6 + + + id + 1 + + + TEXT|0s + 1 + + + TEXT|0s + 2 + + + TEXT|0s + 3 + + + INT|0s + 4 + + + TEXT|0s + 5 +
\ No newline at end of file diff --git a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat b/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat deleted file mode 100644 index 4381c2c..0000000 Binary files a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat and /dev/null differ diff --git a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.len b/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.len deleted file mode 100644 index 2409504..0000000 Binary files a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.len and /dev/null differ diff --git a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values b/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values deleted file mode 100644 index b7d7387..0000000 Binary files a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values and /dev/null differ diff --git a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values.at b/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values.at deleted file mode 100644 index 9f0d9e8..0000000 Binary files a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values.at and /dev/null differ diff --git a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values.s b/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values.s deleted file mode 100644 index 1d6fe36..0000000 --- a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat.values.s +++ /dev/null @@ -1 +0,0 @@ -Y \ No newline at end of file diff --git a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat_i b/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat_i deleted file mode 100644 index 075d1ee..0000000 Binary files a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat_i and /dev/null differ diff --git a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat_i.len b/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat_i.len deleted file mode 100644 index 131e265..0000000 Binary files a/.idea/dataSources/bcae6d7f-dd17-487e-ab3f-743f9b393d89/entities/entities.dat_i.len and /dev/null differ diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index e5abd44..a99e3d6 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -21,6 +21,11 @@
\ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/Bot.java b/src/main/java/dev/cosgy/TextToSpeak/Bot.java deleted file mode 100644 index 0a53c6d..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/Bot.java +++ /dev/null @@ -1,186 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak; - -import com.jagrosh.jdautilities.commons.waiter.EventWaiter; -import dev.cosgy.TextToSpeak.audio.*; -import dev.cosgy.TextToSpeak.gui.GUI; -import dev.cosgy.TextToSpeak.settings.SettingsManager; -import dev.cosgy.TextToSpeak.settings.UserSettingsManager; -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.Activity; -import net.dv8tion.jda.api.entities.Guild; -import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.util.Locale; -import java.util.Objects; -import java.util.ResourceBundle; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import static org.slf4j.LoggerFactory.getLogger; - - -public class Bot { - public static Bot INSTANCE; - private final EventWaiter waiter; - private final ScheduledExecutorService threadpool; - private final BotConfig config; - private final SettingsManager settings; - private final ResourceBundle lang; - private final PlayerManager players; - private final VoiceCreation voiceCreation; - private final UserSettingsManager userSettingsManager; - private final AloneInVoiceHandler aloneInVoiceHandler; - Logger log = getLogger(this.getClass()); - private Dictionary dictionary; - private boolean shuttingDown = false; - private JDA jda; - private GUI gui; - - public Bot(EventWaiter waiter, BotConfig config, SettingsManager settings) { - this.waiter = waiter; - this.lang = ResourceBundle.getBundle("lang.yomiage", Locale.JAPAN); - this.config = config; - this.settings = settings; - this.threadpool = Executors.newSingleThreadScheduledExecutor(); - this.players = new PlayerManager(this); - this.players.init(); - this.voiceCreation = new VoiceCreation(this); - this.userSettingsManager = new UserSettingsManager(); - this.aloneInVoiceHandler = new AloneInVoiceHandler(this); - this.aloneInVoiceHandler.init(); - } - - public JDA getJDA() { - return jda; - } - - public void setJDA(JDA jda) { - this.jda = jda; - } - - public void readyJDA() { - this.dictionary = Dictionary.getInstance(this); - } - - public void closeAudioConnection(long guildId) { - Guild guild = jda.getGuildById(guildId); - if (guild != null) - threadpool.submit(() -> guild.getAudioManager().closeAudioConnection()); - } - - public void resetGame() { - Activity game = config.getGame() == null || config.getGame().getName().toLowerCase().matches("(none|なし)") ? null : config.getGame(); - if (!Objects.equals(jda.getPresence().getActivity(), game)) - jda.getPresence().setActivity(game); - } - - public void shutdown() { - if (shuttingDown) - return; - shuttingDown = true; - threadpool.shutdownNow(); - if (jda.getStatus() != JDA.Status.SHUTTING_DOWN) { - jda.getGuilds().forEach(g -> - { - g.getAudioManager().closeAudioConnection(); - AudioHandler ah = (AudioHandler) g.getAudioManager().getSendingHandler(); - if (ah != null) { - ah.stopAndClear(); - ah.getPlayer().destroy(); - } - }); - - // ここからJDAの終了処理 - jda.shutdown(); - try { - if (!jda.awaitShutdown(10, TimeUnit.SECONDS)) { - jda.shutdownNow(); - if (!jda.awaitShutdown(10, TimeUnit.SECONDS)) { - log.warn("JDAのシャットダウンがタイムアウトしました。"); - } - } - } catch (InterruptedException e) { - jda.shutdownNow(); - Thread.currentThread().interrupt(); - log.warn("JDAのシャットダウンが中断されました。"); - } - - // 一時ファイルを削除 - try { - FileUtils.cleanDirectory(new File("tmp")); - FileUtils.cleanDirectory(new File("wav")); - log.info("一時ファイルを削除しました。"); - } catch (IOException e) { - log.warn("一時ファイルの削除に失敗しました。"); - } - - } - if (gui != null) - gui.dispose(); - System.exit(0); - } - - public void setGUI(GUI gui) { - this.gui = gui; - } - - public ResourceBundle GetLang() { - return lang; - } - - public SettingsManager getSettingsManager() { - return settings; - } - - public PlayerManager getPlayerManager() { - return players; - } - - public EventWaiter getWaiter() { - return waiter; - } - - public BotConfig getConfig() { - return config; - } - - public ScheduledExecutorService getThreadpool() { - return threadpool; - } - - public VoiceCreation getVoiceCreation() { - return voiceCreation; - } - - public UserSettingsManager getUserSettingsManager() { - return userSettingsManager; - } - - public Dictionary getDictionary() { - return dictionary; - } - - public AloneInVoiceHandler getAloneInVoiceHandler() { - return aloneInVoiceHandler; - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/BotConfig.java b/src/main/java/dev/cosgy/TextToSpeak/BotConfig.java deleted file mode 100644 index 975ac35..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/BotConfig.java +++ /dev/null @@ -1,202 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak; - -import com.typesafe.config.Config; -import com.typesafe.config.ConfigException; -import com.typesafe.config.ConfigFactory; -import dev.cosgy.TextToSpeak.entities.Prompt; -import dev.cosgy.TextToSpeak.utils.OtherUtil; -import net.dv8tion.jda.api.OnlineStatus; -import net.dv8tion.jda.api.entities.Activity; -import org.apache.commons.io.FileUtils; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; - -public class BotConfig { - private final static String CONTEXT = "Config"; - private final static String START_TOKEN = "/// START OF YOMIAGEBOT CONFIG ///"; - private final static String END_TOKEN = "/// END OF YOMIAGEBOT CONFIG ///"; - private final Prompt prompt; - private Path path = null; - - private String token, prefix, altprefix, dictionary, voiceDirectory, winjtalkdir; - private long owner, aloneTimeUntilStop; - private int maxMessageCount; - private OnlineStatus status; - private Activity game; - private boolean updatealerts, dbots, helpToDm; - - - private boolean valid = false; - - public BotConfig(Prompt prompt) { - this.prompt = prompt; - } - - public void load() { - valid = false; - - try { - path = OtherUtil.getPath(System.getProperty("config.file", System.getProperty("config", "config.txt"))); - if (path.toFile().exists()) { - if (System.getProperty("config.file") == null) - System.setProperty("config.file", System.getProperty("config", "config.txt")); - ConfigFactory.invalidateCaches(); - } - - // load in the config file, plus the default values - //Config config = ConfigFactory.parseFile(path.toFile()).withFallback(ConfigFactory.load()); - Config config = ConfigFactory.load(); - - - token = config.getString("token"); - prefix = config.getString("prefix"); - altprefix = config.getString("altprefix"); - owner = (config.getAnyRef("owner") instanceof String ? 0L : config.getLong("owner")); - game = OtherUtil.parseGame(config.getString("game")); - status = OtherUtil.parseStatus(config.getString("status")); - updatealerts = config.getBoolean("updatealerts"); - dictionary = config.getString("dictionary"); - voiceDirectory = config.getString("voiceDirectory"); - aloneTimeUntilStop = config.getLong("alonetimeuntilstop"); - maxMessageCount = config.getInt("maxmessagecount"); - winjtalkdir = config.getString("winjtalkdir"); - helpToDm = config.getBoolean("helptodm"); - dbots = owner == 334091398263341056L; - - - boolean write = false; - - // validate bot token - if (token == null || token.isEmpty() || token.matches("(BOT_TOKEN_HERE|Botトークンをここに貼り付け)")) { - token = prompt.prompt("BOTトークンを入力してください。" - + "\nBOTトークン: "); - if (token == null) { - prompt.alert(Prompt.Level.WARNING, CONTEXT, "トークンが入力されていません!終了します。\n\n設定ファイルの場所: " + path.toAbsolutePath().toString()); - return; - } else { - write = true; - } - } - - // validate bot owner - if (owner <= 0) { - try { - owner = Long.parseLong(prompt.prompt("所有者のユーザーIDが設定されていない、または有効なIDではありません。" - + "\nBOTの所有者のユーザーIDを入力してください。" - + "\n所有者のユーザーID: ")); - } catch (NumberFormatException | NullPointerException ex) { - owner = 0; - } - if (owner <= 0) { - prompt.alert(Prompt.Level.ERROR, CONTEXT, "無効なユーザーIDです!終了します。\n\n設定ファイルの場所: " + path.toAbsolutePath().toString()); - System.exit(0); - } else { - write = true; - } - } - - if (write) { - String original = OtherUtil.loadResource(this, "/reference.conf"); - String mod; - if (original == null) { - mod = ("token = " + token + "\r\nowner = " + owner); - } else { - mod = original.substring(original.indexOf(START_TOKEN) + START_TOKEN.length(), original.indexOf(END_TOKEN)) - .replace("BOT_TOKEN_HERE", token).replace("Botトークンをここに貼り付け", token) - .replace("0 // OWNER ID", Long.toString(owner)).replace("所有者IDをここに貼り付け", Long.toString(owner)) - .trim(); - } - - FileUtils.writeStringToFile(path.toFile(), mod, StandardCharsets.UTF_8); - } - - // if we get through the whole config, it's good to go - valid = true; - } catch (ConfigException | - IOException ex) { - prompt.alert(Prompt.Level.ERROR, CONTEXT, ex + ": " + ex.getMessage() + "\n\n設定ファイルの場所: " + path.toAbsolutePath().toString()); - } - } - - public boolean isValid() { - return valid; - } - - public String getConfigLocation() { - return path.toFile().getAbsolutePath(); - } - - public String getPrefix() { - return prefix; - } - - public String getAltPrefix() { - return "NONE".equalsIgnoreCase(altprefix) ? null : altprefix; - } - - public String getToken() { - return token; - } - - public long getOwnerId() { - return owner; - } - - public Activity getGame() { - return game; - } - - public boolean getHelpToDm() { - return helpToDm; - } - - public OnlineStatus getStatus() { - return status; - } - - public boolean useUpdateAlerts() { - return updatealerts; - } - - public String getDictionary() { - return dictionary; - } - - public String getVoiceDirectory() { - return voiceDirectory; - } - - public String getWinJTalkDir() { - return winjtalkdir; - } - - public long getAloneTimeUntilStop() { - return aloneTimeUntilStop; - } - - public int getMaxMessageCount() { - return maxMessageCount; - } - - public boolean getDBots() { - return dbots; - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/Listener.java b/src/main/java/dev/cosgy/TextToSpeak/Listener.java deleted file mode 100644 index e4511af..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/Listener.java +++ /dev/null @@ -1,167 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak; - -import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import dev.cosgy.TextToSpeak.audio.AudioHandler; -import dev.cosgy.TextToSpeak.audio.QueuedTrack; -import dev.cosgy.TextToSpeak.settings.Settings; -import dev.cosgy.TextToSpeak.utils.OtherUtil; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent; -import net.dv8tion.jda.api.events.session.ReadyEvent; -import net.dv8tion.jda.api.events.session.ShutdownEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Listener extends ListenerAdapter { - private final Bot bot; - Logger log = getLogger(this.getClass()); - - public Listener(Bot bot) { - this.bot = bot; - } - - @Override - public void onReady(ReadyEvent event) { - if (event.getJDA().getGuilds().isEmpty()) { - Logger log = LoggerFactory.getLogger("TTSBot"); - log.warn("このボットはグループに入っていません!ボットをあなたのグループに追加するには、以下のリンクを使用してください。"); - log.warn(event.getJDA().getInviteUrl(TextToSpeak.RECOMMENDED_PERMS)); - } - if (bot.getConfig().useUpdateAlerts()) { - bot.getThreadpool().scheduleWithFixedDelay(() -> - { - User owner = bot.getJDA().getUserById(bot.getConfig().getOwnerId()); - if (owner != null) { - String currentVersion = OtherUtil.getCurrentVersion(); - String latestVersion = OtherUtil.getLatestVersion(); - if (latestVersion != null && !currentVersion.equalsIgnoreCase(latestVersion) && TextToSpeak.CHECK_UPDATE) { - String msg = String.format(OtherUtil.NEW_VERSION_AVAILABLE, currentVersion, latestVersion); - owner.openPrivateChannel().queue(pc -> pc.sendMessage(msg).queue()); - } - } - }, 0, 24, TimeUnit.HOURS); - } - } - - @Override - public void onGuildVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) { - Member botMember = event.getGuild().getSelfMember(); - - Settings settings = bot.getSettingsManager().getSettings(event.getGuild()); - - if (event.getChannelLeft() != null) { - if (settings.isJoinAndLeaveRead() && Objects.requireNonNull(event.getGuild().getSelfMember().getVoiceState()).getChannel() == event.getChannelLeft() && event.getChannelLeft().getMembers().size() > 1) { - String file; - try { - file = bot.getVoiceCreation().createVoice(event.getGuild(), event.getMember().getUser(), event.getMember().getUser().getName() + "がボイスチャンネルから退出しました。"); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - bot.getPlayerManager().loadItemOrdered(event.getGuild(), file, new ResultHandler(null, event)); - } - - if (event.getChannelLeft().getMembers().size() == 1 && event.getChannelLeft().getMembers().contains(botMember)) { - AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler(); - handler.getQueue().clear(); - try { - bot.getVoiceCreation().clearGuildFolder(event.getGuild()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - if (event.getChannelJoined() != null) { - if (settings.isJoinAndLeaveRead() && Objects.requireNonNull(event.getGuild().getSelfMember().getVoiceState()).getChannel() == event.getChannelJoined()) { - String file; - try { - file = bot.getVoiceCreation().createVoice(event.getGuild(), event.getMember().getUser(), event.getMember().getUser().getName() + "がボイスチャンネルに参加しました。"); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - bot.getPlayerManager().loadItemOrdered(event.getGuild(), file, new ResultHandler(null, event)); - } - } - } - - @Override - public void onShutdown(@NotNull ShutdownEvent event) { - bot.shutdown(); - } - - - private class ResultHandler implements AudioLoadResultHandler { - private final Message m; - private final GuildVoiceUpdateEvent event; - - private ResultHandler(Message m, GuildVoiceUpdateEvent event) { - this.m = m; - this.event = event; - } - - private void loadSingle(AudioTrack track, AudioPlaylist playlist) { - AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler(); - handler.addTrack(new QueuedTrack(track, event.getMember().getUser())); - } - - private int loadPlaylist(AudioPlaylist playlist, AudioTrack exclude) { - int[] count = {0}; - playlist.getTracks().forEach((track) -> { - if (!track.equals(exclude)) { - AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler(); - handler.addTrack(new QueuedTrack(track, event.getMember().getUser())); - count[0]++; - } - }); - return count[0]; - } - - @Override - public void trackLoaded(AudioTrack track) { - loadSingle(track, null); - } - - @Override - public void playlistLoaded(AudioPlaylist playlist) { - - } - - @Override - public void noMatches() { - } - - @Override - public void loadFailed(FriendlyException throwable) { - - } - } -} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/TextToSpeak.java b/src/main/java/dev/cosgy/TextToSpeak/TextToSpeak.java deleted file mode 100644 index 04c09a3..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/TextToSpeak.java +++ /dev/null @@ -1,196 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak; - -import com.github.lalyos.jfiglet.FigletFont; -import com.jagrosh.jdautilities.command.CommandClientBuilder; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.commons.waiter.EventWaiter; -import dev.cosgy.TextToSpeak.commands.admin.GuildSettings; -import dev.cosgy.TextToSpeak.commands.admin.JLReadCmd; -import dev.cosgy.TextToSpeak.commands.admin.SetReadNameCmd; -import dev.cosgy.TextToSpeak.commands.admin.SettcCmd; -import dev.cosgy.TextToSpeak.commands.dictionary.AddWordCmd; -import dev.cosgy.TextToSpeak.commands.dictionary.DlWordCmd; -import dev.cosgy.TextToSpeak.commands.dictionary.WordListCmd; -import dev.cosgy.TextToSpeak.commands.general.*; -import dev.cosgy.TextToSpeak.commands.owner.ShutdownCmd; -import dev.cosgy.TextToSpeak.entities.Prompt; -import dev.cosgy.TextToSpeak.gui.GUI; -import dev.cosgy.TextToSpeak.listeners.CommandAudit; -import dev.cosgy.TextToSpeak.listeners.MessageListener; -import dev.cosgy.TextToSpeak.settings.SettingsManager; -import dev.cosgy.TextToSpeak.utils.OtherUtil; -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.JDABuilder; -import net.dv8tion.jda.api.OnlineStatus; -import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.Activity; -import net.dv8tion.jda.api.requests.GatewayIntent; -import net.dv8tion.jda.api.utils.cache.CacheFlag; -import org.slf4j.Logger; - -import java.awt.*; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.slf4j.LoggerFactory.getLogger; - -public class TextToSpeak { - public final static Permission[] RECOMMENDED_PERMS = {Permission.VIEW_CHANNEL, Permission.MESSAGE_SEND, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION, - Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_MANAGE, Permission.MESSAGE_EXT_EMOJI, Permission.USE_APPLICATION_COMMANDS, - Permission.MANAGE_CHANNEL, Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE}; - public final static GatewayIntent[] INTENTS = {GatewayIntent.DIRECT_MESSAGES, GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGE_REACTIONS, GatewayIntent.GUILD_VOICE_STATES, GatewayIntent.MESSAGE_CONTENT}; - public static boolean CHECK_UPDATE = true; - public static boolean COMMAND_AUDIT_ENABLED = false; - - /** - * @param args コマンドライン引数 - */ - public static void main(String[] args) { - Logger log = getLogger("立ち上げ"); - try { - System.out.println(FigletFont.convertOneLine("Yomiage Bot v" + OtherUtil.getCurrentVersion()) + "\n" + "by Cosgy Dev"); - } catch (IOException ignored) { - } - - Prompt prompt = new Prompt("TextToSpeak Bot", "noguiモードに切り替えます。 -Dnogui=trueフラグを含めると、手動でnoguiモードで起動できます。", - "true".equalsIgnoreCase(System.getProperty("nogui", "false"))); - - // check deprecated nogui mode (new way of setting it is -Dnogui=true) - for (String arg : args) - if ("-nogui".equalsIgnoreCase(arg)) { - prompt.alert(Prompt.Level.WARNING, "GUI", "-noguiフラグは廃止予定です。 " - + "jarの名前の前に-Dnogui = trueフラグを使用してください。 例:java -jar -Dnogui=true JMusicBot.jar"); - } else if ("-nocheckupdates".equalsIgnoreCase(arg)) { - CHECK_UPDATE = false; - log.info("アップデートチェックを無効にしました"); - } else if ("-auditcommands".equalsIgnoreCase(arg)) { - COMMAND_AUDIT_ENABLED = true; - log.info("実行されたコマンドの記録を有効にしました。"); - } - - String version = OtherUtil.checkVersion(prompt); - - if (!System.getProperty("java.vm.name").contains("64")) - prompt.alert(Prompt.Level.WARNING, "Java Version", "サポートされていないJavaバージョンを使用しています。64ビット版のJavaを使用してください。"); - - BotConfig config = new BotConfig(prompt); - config.load(); - if (!config.isValid()) - return; - - EventWaiter waiter = new EventWaiter(); - SettingsManager settings = new SettingsManager(); - Bot bot = new Bot(waiter, config, settings); - Bot.INSTANCE = bot; - - AboutCommand aboutCommand = new AboutCommand(Color.BLUE.brighter(), - bot.GetLang().getString("appName") + "(v" + version + ")", - RECOMMENDED_PERMS); - aboutCommand.setIsAuthor(false); - aboutCommand.setReplacementCharacter("🎶"); - - - CommandClientBuilder cb = new CommandClientBuilder() - .setPrefix(config.getPrefix()) - .setAlternativePrefix(config.getAltPrefix()) - .setOwnerId(Long.toString(config.getOwnerId())) - //.setEmojis(config.getSuccess(), config.getWarning(), config.getError()) - .setHelpWord("help") - .setLinkedCacheSize(200) - .setGuildSettingsManager(settings) - .setListener(new CommandAudit()); - - List slashCommandList = new ArrayList<>() {{ - add(aboutCommand); - add(new HelpCmd(bot)); - add(new JoinCmd(bot)); - add(new ByeCmd(bot)); - add(new SettingsCmd(bot)); - add(new SetVoiceCmd(bot)); - add(new SetSpeedCmd(bot)); - add(new SetIntonationCmd(bot)); - add(new SetVoiceQualityA(bot)); - add(new SetVoiceQualityFm(bot)); - add(new AddWordCmd(bot)); - add(new WordListCmd(bot)); - add(new DlWordCmd(bot)); - add(new SettcCmd(bot)); - add(new SetReadNameCmd(bot)); - add(new JLReadCmd(bot)); - add(new GuildSettings(bot)); - add(new ShutdownCmd(bot)); - }}; - cb.addSlashCommands(slashCommandList.toArray(new SlashCommand[0])); - cb.addCommands(slashCommandList.toArray(new SlashCommand[0])); - - boolean nogame = false; - if (config.getStatus() != OnlineStatus.UNKNOWN) - cb.setStatus(config.getStatus()); - if (config.getGame() == null) - cb.setActivity(Activity.playing("/helpでヘルプを確認")); - else if (config.getGame().getName().toLowerCase().matches("(none|なし)")) { - cb.setActivity(null); - nogame = true; - } else - cb.setActivity(config.getGame()); - - if (!prompt.isNoGUI()) { - try { - GUI gui = new GUI(bot); - bot.setGUI(gui); - gui.init(); - } catch (Exception e) { - log.error("GUIを開くことができませんでした。次の要因が考えられます:\n" - + "サーバー上で実行している\n" - + "画面がない環境下で実行している\n" - + "このエラーを非表示にするには、 -Dnogui=true フラグを使用してGUIなしモードで実行してください。"); - } - } - - log.info(config.getConfigLocation() + " から設定を読み込みました"); - - try { - JDA jda = JDABuilder.create(config.getToken(), Arrays.asList(INTENTS)) - .enableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE) - .disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS, CacheFlag.EMOJI, CacheFlag.ONLINE_STATUS, CacheFlag.STICKER) - .setActivity(nogame ? null : Activity.playing("準備中...")) - .setStatus(config.getStatus() == OnlineStatus.INVISIBLE || config.getStatus() == OnlineStatus.OFFLINE - ? OnlineStatus.INVISIBLE : OnlineStatus.DO_NOT_DISTURB) - .addEventListeners(cb.build(), waiter, new Listener(bot), new MessageListener(bot)) - .setBulkDeleteSplittingEnabled(true) - .build(); - - bot.setJDA(jda); - } /*catch (LoginException ex) { -prompt.alert(Prompt.Level.ERROR, bot.GetLang().getString("appName"), ex + "\n" + -"正しい設定ファイルを編集していることを確認してください。Botトークンでのログインに失敗しました。" + -"正しいBotトークンを入力してください。(CLIENT SECRET ではありません!)\n" + -"設定ファイルの場所: " + config.getConfigLocation()); -System.exit(1); -}*/ catch (IllegalArgumentException ex) { - prompt.alert(Prompt.Level.ERROR, bot.GetLang().getString("appName"), "設定の一部が無効です:" + ex + "\n" + - "設定ファイルの場所: " + config.getConfigLocation()); - System.exit(1); - } - - Runtime.getRuntime().addShutdownHook(new Thread(bot::shutdown)); - } -} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/audio/AloneInVoiceHandler.java b/src/main/java/dev/cosgy/TextToSpeak/audio/AloneInVoiceHandler.java deleted file mode 100644 index 0abdcc4..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/audio/AloneInVoiceHandler.java +++ /dev/null @@ -1,87 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.audio; - -import dev.cosgy.TextToSpeak.Bot; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent; - -import java.time.Instant; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public class AloneInVoiceHandler { - private final Bot bot; - private final HashMap aloneSince = new HashMap<>(); - private long aloneTimeUntilStop = 0; - - public AloneInVoiceHandler(Bot bot) { - this.bot = bot; - } - - public void init() { - aloneTimeUntilStop = bot.getConfig().getAloneTimeUntilStop(); - if (aloneTimeUntilStop > 0) - bot.getThreadpool().scheduleWithFixedDelay(this::check, 0, 5, TimeUnit.SECONDS); - } - - private void check() { - Set toRemove = new HashSet<>(); - for (Map.Entry entrySet : aloneSince.entrySet()) { - if (entrySet.getValue().getEpochSecond() > Instant.now().getEpochSecond() - aloneTimeUntilStop) continue; - - Guild guild = bot.getJDA().getGuildById(entrySet.getKey()); - - if (guild == null) { - toRemove.add(entrySet.getKey()); - continue; - } - - ((AudioHandler) guild.getAudioManager().getSendingHandler()).stopAndClear(); - guild.getAudioManager().closeAudioConnection(); - - toRemove.add(entrySet.getKey()); - } - toRemove.forEach(aloneSince::remove); - } - - public void onVoiceUpdate(GuildVoiceUpdateEvent event) { - if (aloneTimeUntilStop <= 0) return; - - Guild guild = event.getEntity().getGuild(); - if (!bot.getPlayerManager().hasHandler(guild)) return; - - boolean alone = isAlone(guild); - boolean inList = aloneSince.containsKey(guild.getIdLong()); - - if (!alone && inList) - aloneSince.remove(guild.getIdLong()); - else if (alone && !inList) - aloneSince.put(guild.getIdLong(), Instant.now()); - } - - private boolean isAlone(Guild guild) { - if (guild.getAudioManager().getConnectedChannel() == null) return false; - return guild.getAudioManager().getConnectedChannel().getMembers().stream() - .noneMatch(x -> - !x.getVoiceState().isDeafened() - && !x.getUser().isBot()); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/audio/AudioHandler.java b/src/main/java/dev/cosgy/TextToSpeak/audio/AudioHandler.java deleted file mode 100644 index fc3230c..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/audio/AudioHandler.java +++ /dev/null @@ -1,127 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.audio; - -import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; -import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; -import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; -import dev.cosgy.TextToSpeak.queue.FairQueue; -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.audio.AudioSendHandler; -import net.dv8tion.jda.api.entities.Guild; - -import java.nio.ByteBuffer; -import java.util.HashSet; -import java.util.Set; - -public class AudioHandler extends AudioEventAdapter implements AudioSendHandler { - private final FairQueue queue = new FairQueue<>(); - private final Set votes = new HashSet<>(); - - private final PlayerManager manager; - private final AudioPlayer audioPlayer; - private final long guildId; - private final String stringGuildId; - - private AudioFrame lastFrame; - - protected AudioHandler(PlayerManager manager, Guild guild, AudioPlayer player) { - this.manager = manager; - this.audioPlayer = player; - this.guildId = guild.getIdLong(); - this.stringGuildId = guild.getId(); - } - - public int addTrackToFront(QueuedTrack qtrack) { - if (audioPlayer.getPlayingTrack() == null) { - audioPlayer.playTrack(qtrack.getTrack()); - return -1; - } else { - queue.addAt(0, qtrack); - return 0; - } - } - - public int addTrack(QueuedTrack qtrack) { - if (audioPlayer.getPlayingTrack() == null) { - audioPlayer.playTrack(qtrack.getTrack()); - return -1; - } else - return queue.add(qtrack); - } - - public FairQueue getQueue() { - return queue; - } - - public void stopAndClear() { - queue.clear(); - audioPlayer.stopTrack(); - } - - public boolean isVoiceListening(JDA jda) { - return guild(jda).getSelfMember().getVoiceState().inAudioChannel() && audioPlayer.getPlayingTrack() != null; - } - - public Set getVotes() { - return votes; - } - - public AudioPlayer getPlayer() { - return audioPlayer; - } - - public long getRequester() { - if (audioPlayer.getPlayingTrack() == null || audioPlayer.getPlayingTrack().getUserData(Long.class) == null) - return 0; - return audioPlayer.getPlayingTrack().getUserData(Long.class); - } - - // Audio Events - @Override - public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { - if (!queue.isEmpty()) { - QueuedTrack qt = queue.pull(); - player.playTrack(qt.getTrack()); - } - } - - // Audio Send Handler methods - @Override - public boolean canProvide() { - lastFrame = audioPlayer.provide(); - return lastFrame != null; - } - - @Override - public ByteBuffer provide20MsAudio() { - return ByteBuffer.wrap(lastFrame.getData()); - } - - @Override - public boolean isOpus() { - return true; - } - - - // Private methods - private Guild guild(JDA jda) { - return jda.getGuildById(guildId); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/audio/Dictionary.java b/src/main/java/dev/cosgy/TextToSpeak/audio/Dictionary.java deleted file mode 100644 index 12a16f1..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/audio/Dictionary.java +++ /dev/null @@ -1,167 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.audio; - -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.utils.OtherUtil; -import net.dv8tion.jda.api.entities.Guild; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.file.Path; -import java.sql.*; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -public class Dictionary { - private static Dictionary instance; - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private final Bot bot; - private final Path path; - private final boolean create; - private final Connection connection; - private final ConcurrentHashMap> guildDic; - - private Dictionary(Bot bot) { - this.bot = bot; - this.guildDic = new ConcurrentHashMap<>(); - this.path = OtherUtil.getPath("UserData.sqlite"); - this.create = !path.toFile().exists(); - - try { - Class.forName("org.sqlite.JDBC"); - connection = DriverManager.getConnection("jdbc:sqlite:UserData.sqlite"); - Statement statement = connection.createStatement(); - statement.executeUpdate("CREATE TABLE IF NOT EXISTS Dictionary(guild_id integer,word text,reading)"); - - List guilds = bot.getJDA().getGuilds(); - for (Guild value : guilds) { - long guildId = value.getIdLong(); - Optional> optionalHashMap = getWordsFromDatabase(guildId); - optionalHashMap.ifPresent(hashMap -> guildDic.put(guildId, hashMap)); - } - } catch (SQLException | ClassNotFoundException e) { - logger.error("An error occurred while initializing the dictionary: ", e); - throw new IllegalStateException(e); - } - logger.info("Dictionary initialization completed."); - } - - public static Dictionary getInstance(Bot bot) { - if (instance == null) { - instance = new Dictionary(bot); - } - return instance; - } - - /** - * データベースとHashMapの内容を更新または新規追加します。 - * - * @param guildId サーバーID - * @param word 単語 - * @param reading 読み方 - */ - public synchronized void updateDictionary(Long guildId, String word, String reading) { - guildDic.compute(guildId, (k, v) -> { - HashMap words = v != null ? v : new HashMap<>(); - words.put(word, reading); - executeUpdate(guildId, word, reading); - return words; - }); - } - - /** - * データベースに登録されている単語を削除します。 - * - * @param guildId サーバーID - * @param word 単語 - * @return 正常に削除できた場合は {@code true}、削除時に問題が発生した場合は{@code false}を返します。 - */ - public synchronized boolean deleteDictionary(Long guildId, String word) { - guildDic.compute(guildId, (k, v) -> { - if (v == null || !v.containsKey(word)) { - return null; - } - HashMap words = new HashMap<>(v); - words.remove(word); - executeDelete(guildId, word); - return words; - }); - return true; - } - - /** - * サーバーの辞書データを取得します。 - * - * @param guildId サーバーID - * @return {@code HashMap}形式の変数を返します。 - */ - public HashMap getWords(Long guildId) { - return guildDic.getOrDefault(guildId, new HashMap<>()); - } - - private Optional> getWordsFromDatabase(Long guildId) { - String sql = "SELECT * FROM Dictionary WHERE guild_id = ?"; - try (PreparedStatement ps = connection.prepareStatement(sql)) { - ps.setLong(1, guildId); - ResultSet rs = ps.executeQuery(); - HashMap word = new HashMap<>(); - while (rs.next()) { - word.put(rs.getString(2), rs.getString(3)); - } - return Optional.of(word); - } catch (SQLException throwables) { - logger.error("An error occurred while retrieving data from the dictionary: ", throwables); - return Optional.empty(); - } - } - - private void executeUpdate(Long guildId, String word, String reading) { - String sql = "INSERT INTO Dictionary VALUES (?,?,?) ON CONFLICT (guild_id, word) DO UPDATE SET reading = ?"; - try (PreparedStatement ps = connection.prepareStatement(sql)) { - ps.setLong(1, guildId); - ps.setString(2, word); - ps.setString(3, reading); - ps.setString(4, reading); - ps.executeUpdate(); - } catch (SQLException throwables) { - logger.error("An error occurred while updating the dictionary: ", throwables); - } - } - - private void executeDelete(Long guildId, String word) { - String sql = "DELETE FROM Dictionary WHERE guild_id = ? AND word = ?"; - try (PreparedStatement ps = connection.prepareStatement(sql)) { - ps.setLong(1, guildId); - ps.setString(2, word); - ps.executeUpdate(); - } catch (SQLException throwables) { - logger.error("An error occurred while deleting from the dictionary: ", throwables); - } - } - - public void close() { - try { - connection.close(); - } catch (SQLException throwables) { - logger.error("An error occurred while closing the database connection: ", throwables); - } - } -} - diff --git a/src/main/java/dev/cosgy/TextToSpeak/audio/PlayerManager.java b/src/main/java/dev/cosgy/TextToSpeak/audio/PlayerManager.java deleted file mode 100644 index 4632951..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/audio/PlayerManager.java +++ /dev/null @@ -1,70 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.audio; - -import com.sedmelluq.discord.lavaplayer.player.AudioConfiguration; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; -import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; -import dev.cosgy.TextToSpeak.Bot; -import net.dv8tion.jda.api.entities.Guild; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PlayerManager extends DefaultAudioPlayerManager { - private final Bot bot; - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - public PlayerManager(Bot bot) { - this.bot = bot; - } - - public void init() { - AudioSourceManagers.registerLocalSource(this); - - if (getConfiguration().getOpusEncodingQuality() != 10) { - logger.debug("OpusEncodingQuality は、" + getConfiguration().getOpusEncodingQuality() + "(< 10)" + ", 品質を10に設定します。"); - getConfiguration().setOpusEncodingQuality(10); - } - - if (getConfiguration().getResamplingQuality() != AudioConfiguration.ResamplingQuality.HIGH) { - logger.debug("ResamplingQuality は " + getConfiguration().getResamplingQuality().name() + "(HIGHではない), 品質をHIGHに設定します。"); - getConfiguration().setResamplingQuality(AudioConfiguration.ResamplingQuality.HIGH); - } - } - - public Bot getBot() { - return bot; - } - - public boolean hasHandler(Guild guild) { - return guild.getAudioManager().getSendingHandler() != null; - } - - public AudioHandler setUpHandler(Guild guild) { - AudioHandler handler; - if (guild.getAudioManager().getSendingHandler() == null) { - AudioPlayer player = createPlayer(); - player.setVolume(bot.getSettingsManager().getSettings(guild).getVolume()); - handler = new AudioHandler(this, guild, player); - player.addListener(handler); - guild.getAudioManager().setSendingHandler(handler); - } else - handler = (AudioHandler) guild.getAudioManager().getSendingHandler(); - return handler; - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/audio/VoiceCreation.java b/src/main/java/dev/cosgy/TextToSpeak/audio/VoiceCreation.java deleted file mode 100644 index fdcf533..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/audio/VoiceCreation.java +++ /dev/null @@ -1,194 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.audio; - -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.settings.UserSettings; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.User; -import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class VoiceCreation { - private static final Logger logger = LoggerFactory.getLogger(VoiceCreation.class); - private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().startsWith("win"); - - // 各種設定の値を保持するためのフィールド - private final Bot bot; - private final String dictionary; - private final String voiceDirectory; - private final String winJTalkDir; - private final int maxMessageCount; - - // 初期化処理を行うメソッド - public VoiceCreation(Bot bot) { - this.bot = bot; - this.dictionary = bot.getConfig().getDictionary(); - this.voiceDirectory = bot.getConfig().getVoiceDirectory(); - this.winJTalkDir = bot.getConfig().getWinJTalkDir(); - this.maxMessageCount = bot.getConfig().getMaxMessageCount(); - } - - public String createVoice(Guild guild, User user, String message) throws IOException, InterruptedException { - // ファイル名やパスの生成に使用するIDを生成する - String guildId = guild.getId(); - String fileId = UUID.randomUUID().toString(); - String fileName = "wav" + File.separator + guildId + File.separator + fileId + ".wav"; - - // 必要なディレクトリを作成する - createDirectories(guildId); - - // ユーザーの設定を取得する - UserSettings settings = bot.getUserSettingsManager().getSettings(user.getIdLong()); - - // 辞書データを取得し、メッセージを変換する - HashMap words = bot.getDictionary().getWords(guild.getIdLong()); - String dicMsg = sanitizeMessage(message); - for (Map.Entry entry : words.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - dicMsg = dicMsg.replaceAll(key, value); - } - String tmpFilePath = createTmpTextFile(guildId, fileId, dicMsg); - - - // コマンドを生成して実行する - String[] command = getCommand(settings, tmpFilePath, fileName); - ProcessBuilder builder = new ProcessBuilder(command); - - builder.redirectErrorStream(true); - logger.debug("Command: " + String.join(" ", command)); - Process process = builder.start(); - process.waitFor(); - - return fileName; - } - - // メッセージをサニタイズするメソッド - private String sanitizeMessage(String message) { - String sanitizedMsg = message.replaceAll("[\\uD800-\\uDFFF]", " "); - sanitizedMsg = sanitizedMsg.replaceAll("Kosugi_kun", "コスギクン"); - return sanitizedMsg; - } - - // テキストファイルを作成するメソッド - private String createTmpTextFile(String guildId, String fileId, String message) throws FileNotFoundException, UnsupportedEncodingException { - String filePath = "tmp" + File.separator + guildId + File.separator + fileId + ".txt"; - try (PrintWriter writer = new PrintWriter(filePath, getCharacterCode())) { - writer.write(message); - } - return filePath; - } - - // 文字コードを取得するメソッド - private String getCharacterCode() { - return IS_WINDOWS ? "Shift-JIS" : "UTF-8"; - } - - // コマンドを生成するメソッド - private String[] getCommand(UserSettings settings, String tmpFilePath, String fileName) { - ArrayList command = new ArrayList<>(); - command.add(getOpenJTalkExecutable()); - command.add("-x"); - command.add(dictionary); - command.add("-m"); - command.add(getVoiceFilePath(settings.getVoice())); - command.add("-ow"); - command.add(fileName); - command.add("-r"); - command.add(String.valueOf(settings.getSpeed())); - command.add("-jf"); - command.add(String.valueOf(settings.getIntonation())); - command.add("-a"); - command.add(String.valueOf(settings.getVoiceQualityA())); - command.add("-fm"); - command.add(String.valueOf(settings.getVoiceQualityFm())); - command.add(tmpFilePath); - - return command.toArray(new String[0]); - } - - private String getOpenJTalkExecutable() { - if (IS_WINDOWS) { - return Paths.get(winJTalkDir, "open_jtalk.exe").toString(); - } else { - return "open_jtalk"; - } - } - - private String getVoiceFilePath(String voice) { - return Paths.get(voiceDirectory, voice + ".htsvoice").toString(); - } - - // 必要なディレクトリを作成するメソッド - private void createDirectories(String guildId) throws IOException { - createDirectory("tmp"); - createDirectory("tmp" + File.separator + guildId); - createDirectory("wav"); - createDirectory("wav" + File.separator + guildId); - } - - // ディレクトリを作成するメゾット - private void createDirectory(String directory) throws IOException { - Path path = Paths.get(directory); - if (!Files.exists(path)) { - Files.createDirectory(path); - logger.info("Created directory: " + directory); - } - } - - // ギルドに関連する一時ファイルや音声ファイルを削除するメソッド - public void clearGuildFolder(Guild guild) throws IOException { - String guildId = guild.getId(); - Path tmpPath = Paths.get("tmp" + File.separator + guildId); - Path wavPath = Paths.get("wav" + File.separator + guildId); - if (Files.exists(tmpPath)) { - FileUtils.cleanDirectory(tmpPath.toFile()); - logger.info("Cleared temporary files for guild: " + guildId); - } - - if (Files.exists(wavPath)) { - FileUtils.cleanDirectory(wavPath.toFile()); - logger.info("Cleared WAV files for guild: " + guildId); - } - } - - // 利用可能な音声名を取得するメソッド - public ArrayList getVoices() { - FilenameFilter filter = (file, str) -> str.endsWith("htsvoice"); - File dir = new File(voiceDirectory); - File[] list = dir.listFiles(filter); - ArrayList voices = new ArrayList<>(); - - for (File file : list) { - voices.add(file.getName().replace(".htsvoice", "")); - } - - logger.debug("Available voices: " + voices.toString()); - return voices; - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/AdminCommand.java b/src/main/java/dev/cosgy/TextToSpeak/commands/AdminCommand.java deleted file mode 100644 index 4ff6436..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/AdminCommand.java +++ /dev/null @@ -1,45 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands; - -import com.jagrosh.jdautilities.command.CommandClient; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import net.dv8tion.jda.api.Permission; - -public abstract class AdminCommand extends SlashCommand { - public AdminCommand() { - this.category = new Category("管理", event -> - { - if (event.isOwner() || event.getMember().isOwner()) - return true; - if (event.getGuild() == null) - return true; - return event.getMember().hasPermission(Permission.MANAGE_SERVER); - }); - this.guildOnly = true; - this.userPermissions = new Permission[]{Permission.ADMINISTRATOR}; - } - - public static boolean checkAdminPermission(CommandClient client, SlashCommandEvent event) { - if (event.getUser().getId().equals(client.getOwnerId()) || event.getMember().isOwner()) - return true; - if (event.getGuild() == null) - return true; - return event.getMember().hasPermission(Permission.MANAGE_SERVER); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/admin/GuildSettings.java b/src/main/java/dev/cosgy/TextToSpeak/commands/admin/GuildSettings.java deleted file mode 100644 index 5eed2eb..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/admin/GuildSettings.java +++ /dev/null @@ -1,84 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.admin; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.commands.AdminCommand; -import dev.cosgy.TextToSpeak.settings.Settings; -import net.dv8tion.jda.api.EmbedBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.awt.*; -import java.util.Objects; - -public class GuildSettings extends AdminCommand { - private final Bot bot; - Logger log = LoggerFactory.getLogger(this.getClass()); - - public GuildSettings(Bot bot) { - this.bot = bot; - this.name = "gsettings"; - this.help = "ギルドの現在の設定を確認できます。"; - } - - @Override - protected void execute(SlashCommandEvent event) { - if (!checkAdminPermission(event.getClient(), event)) { - event.reply(event.getClient().getWarning() + "権限がないため実行できません。").queue(); - return; - } - - Settings settings = bot.getSettingsManager().getSettings(event.getGuild()); - - String text = "null"; - if (settings.getTextChannel(event.getGuild()) != null) { - text = settings.getTextChannel(event.getGuild()).getName(); - } - - EmbedBuilder ebuilder = new EmbedBuilder() - .setColor(Color.orange) - .setTitle(event.getGuild().getName() + "の設定") - .addField("ユーザー名読み上げ:", String.valueOf(Objects.requireNonNull(settings).isReadName()), false) - .addField("参加、退出時の読み上げ:", String.valueOf(settings.isJoinAndLeaveRead()), false) - //.addField("接頭語:", settings.getPrefix(), false) - .addField("読み上げるチャンネル:", text, false) - .addField("読み上げの主音量:", String.valueOf(settings.getVolume()), false); - event.replyEmbeds(ebuilder.build()).queue(); - } - - @Override - protected void execute(CommandEvent event) { - Settings settings = bot.getSettingsManager().getSettings(event.getGuild()); - String text = "null"; - if (settings.getTextChannel(event.getGuild()) != null) { - text = settings.getTextChannel(event.getGuild()).getName(); - } - - EmbedBuilder ebuilder = new EmbedBuilder() - .setColor(Color.orange) - .setTitle(event.getGuild().getName() + "の設定") - .addField("ユーザー名読み上げ:", String.valueOf(Objects.requireNonNull(settings).isReadName()), false) - .addField("参加、退出時の読み上げ:", String.valueOf(settings.isJoinAndLeaveRead()), false) - //.addField("接頭語:", settings.getPrefix(), false) - .addField("読み上げるチャンネル:", text, false) - .addField("読み上げの主音量:", String.valueOf(settings.getVolume()), false); - event.reply(ebuilder.build()); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/admin/JLReadCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/admin/JLReadCmd.java deleted file mode 100644 index 1b68160..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/admin/JLReadCmd.java +++ /dev/null @@ -1,66 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.admin; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.commands.AdminCommand; -import dev.cosgy.TextToSpeak.settings.Settings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class JLReadCmd extends AdminCommand { - private final Bot bot; - Logger log = LoggerFactory.getLogger(this.getClass()); - - public JLReadCmd(Bot bot) { - this.bot = bot; - this.name = "jlread"; - this.help = "ボイスチャンネルにユーザーが参加または退出した時にユーザー名を読み上げるか否かを設定します。"; - } - - @Override - protected void execute(SlashCommandEvent event) { - if (!checkAdminPermission(event.getClient(), event)) { - event.reply(event.getClient().getWarning() + "権限がないため実行できません。").queue(); - return; - } - Settings settings = bot.getSettingsManager().getSettings(event.getGuild()); - - if (settings.isJoinAndLeaveRead()) { - settings.setJoinAndLeaveRead(false); - event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを無効にしました。").queue(); - } else { - settings.setJoinAndLeaveRead(true); - event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを有効にしました。").queue(); - } - } - - @Override - protected void execute(CommandEvent event) { - Settings settings = bot.getSettingsManager().getSettings(event.getGuild()); - - if (settings.isJoinAndLeaveRead()) { - settings.setJoinAndLeaveRead(false); - event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを無効にしました。"); - } else { - settings.setJoinAndLeaveRead(true); - event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを有効にしました。"); - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/admin/SetReadNameCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/admin/SetReadNameCmd.java deleted file mode 100644 index 48aa98b..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/admin/SetReadNameCmd.java +++ /dev/null @@ -1,66 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.admin; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.commands.AdminCommand; -import dev.cosgy.TextToSpeak.settings.Settings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SetReadNameCmd extends AdminCommand { - private final Bot bot; - Logger log = LoggerFactory.getLogger(this.getClass()); - - public SetReadNameCmd(Bot bot) { - this.bot = bot; - this.name = "setreadname"; - this.help = "テキストを読み上げる際にユーザー名も読み上げるかを設定します。"; - } - - @Override - protected void execute(SlashCommandEvent event) { - if (!checkAdminPermission(event.getClient(), event)) { - event.reply(event.getClient().getWarning() + "権限がないため実行できません。").queue(); - return; - } - Settings settings = bot.getSettingsManager().getSettings(event.getGuild()); - - if (settings.isReadName()) { - settings.setReadName(false); - event.reply("ユーザー名の読み上げを無効にしました。").queue(); - } else { - settings.setReadName(true); - event.reply("ユーザー名の読み上げを有効にしました。").queue(); - } - } - - @Override - protected void execute(CommandEvent event) { - Settings settings = bot.getSettingsManager().getSettings(event.getGuild()); - - if (settings.isReadName()) { - settings.setReadName(false); - event.reply("ユーザー名の読み上げを無効にしました。"); - } else { - settings.setReadName(true); - event.reply("ユーザー名の読み上げを有効にしました。"); - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/admin/SettcCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/admin/SettcCmd.java deleted file mode 100644 index 53b9365..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/admin/SettcCmd.java +++ /dev/null @@ -1,130 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.admin; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import com.jagrosh.jdautilities.commons.utils.FinderUtil; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.commands.AdminCommand; -import dev.cosgy.TextToSpeak.settings.Settings; -import dev.cosgy.TextToSpeak.utils.FormatUtil; -import net.dv8tion.jda.api.entities.channel.ChannelType; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -public class SettcCmd extends AdminCommand { - Logger log = LoggerFactory.getLogger(this.getClass()); - - public SettcCmd(Bot bot) { - this.name = "settc"; - this.help = "読み上げをするチャンネルを設定します。読み上げするチャンネルを設定していない場合は、joinコマンドを最後に実行したチャンネルが読み上げ対象になります。"; - this.arguments = "<チャンネル名|NONE|なし>"; - - this.children = new SlashCommand[]{new Set(), new None()}; - } - - @Override - protected void execute(SlashCommandEvent event) { - } - - @Override - protected void execute(CommandEvent event) { - if (event.getArgs().isEmpty()) { - event.reply(event.getClient().getError() + "チャンネルまたはNONEを含めてください。"); - return; - } - Settings s = event.getClient().getSettingsFor(event.getGuild()); - if (event.getArgs().toLowerCase().matches("(none|なし)")) { - s.setTextChannel(null); - event.reply(event.getClient().getSuccess() + "読み上げをするチャンネルの設定を無効にしました。"); - } else { - List list = FinderUtil.findTextChannels(event.getArgs(), event.getGuild()); - if (list.isEmpty()) - event.reply(event.getClient().getWarning() + "一致するチャンネルが見つかりませんでした \"" + event.getArgs() + "\""); - else if (list.size() > 1) - event.reply(event.getClient().getWarning() + FormatUtil.listOfTChannels(list, event.getArgs())); - else { - s.setTextChannel(list.get(0)); - log.info("読み上げを行うチャンネルを設定しました。"); - event.reply(event.getClient().getSuccess() + "読み上げるチャンネルを<#" + list.get(0).getId() + ">に設定しました。"); - } - } - } - - private static class Set extends AdminCommand { - public Set() { - this.name = "set"; - this.help = "読み上げるチャンネルを設定"; - - List options = new ArrayList<>(); - options.add(new OptionData(OptionType.CHANNEL, "channel", "テキストチャンネル", true)); - - this.options = options; - } - - @Override - protected void execute(SlashCommandEvent event) { - Settings s = event.getClient().getSettingsFor(event.getGuild()); - - - if (event.getOption("channel").getChannelType() != ChannelType.TEXT) { - event.reply(event.getClient().getError() + "テキストチャンネルを設定して下さい。").queue(); - return; - } - Long channelId = event.getOption("channel").getAsLong(); - TextChannel tc = event.getGuild().getTextChannelById(channelId); - - s.setTextChannel(tc); - event.reply(event.getClient().getSuccess() + "読み上げるチャンネルを<#" + tc.getId() + ">に設定しました。").queue(); - - } - } - - private static class None extends AdminCommand { - public None() { - this.name = "none"; - this.help = "読み上げるチャンネル設定をリセットします。"; - } - - @Override - protected void execute(SlashCommandEvent event) { - if (!checkAdminPermission(event.getClient(), event)) { - event.reply(event.getClient().getWarning() + "権限がないため実行できません。").queue(); - return; - } - Settings s = event.getClient().getSettingsFor(event.getGuild()); - s.setTextChannel(null); - event.reply(event.getClient().getSuccess() + "読み上げるチャンネル設定をリセットしました。").queue(); - } - - @Override - protected void execute(CommandEvent event) { - Settings s = event.getClient().getSettingsFor(event.getGuild()); - s.setTextChannel(null); - event.reply(event.getClient().getSuccess() + "読み上げるチャンネル設定をリセットしました。"); - } - } - -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/AddWordCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/AddWordCmd.java deleted file mode 100644 index 0a019e9..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/AddWordCmd.java +++ /dev/null @@ -1,116 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.dictionary; - -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import com.jagrosh.jdautilities.menu.ButtonMenu; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.audio.Dictionary; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.exceptions.PermissionException; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.awt.*; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; - -public class AddWordCmd extends SlashCommand { - private static final Logger logger = LoggerFactory.getLogger(AddWordCmd.class); - private static final Color SUCCESS_COLOR = new Color(0, 163, 129); - private static final Color ERROR_COLOR = Color.RED; - private static final String INVALID_ARGS_MESSAGE = "コマンドが無効です。単語と読み方の2つを入力して実行して下さい。"; - private static final String USAGE_MESSAGE = "使用方法: /addword <単語> <読み方>"; - private static final String KATAKANA_REGEX = "^[ァ-ヶー]*$"; - private final Bot bot; - - public AddWordCmd(Bot bot) { - this.bot = bot; - this.name = "wdad"; - this.help = "辞書に単語を追加します。辞書に単語が存在している場合は上書きされます。"; - this.category = new Category("辞書"); - - List options = new ArrayList<>(); - options.add(new OptionData(OptionType.STRING, "word", "単語", true)); - options.add(new OptionData(OptionType.STRING, "reading", "読み方(カタカナ)", true)); - this.options = options; - } - - private static boolean isKatakana(String str) { - return Pattern.matches(KATAKANA_REGEX, str); - } - - private void handleCommand(SlashCommandEvent event, String word, String reading) { - long guildId = event.getGuild().getIdLong(); - Dictionary dictionary = bot.getDictionary(); - boolean isWordExist = dictionary.getWords(guildId).containsKey(word); - event.deferReply().queue(); - if (isWordExist) { - String no = "❌"; - String ok = "✔"; - new ButtonMenu.Builder() - .setText("単語が既に存在します。上書きしますか?") - .addChoices(no, ok) - .setEventWaiter(bot.getWaiter()) - .setTimeout(30, TimeUnit.SECONDS) - .setAction(re -> { - if (re.getName().equals(ok)) { - dictionary.updateDictionary(guildId, word, reading); - sendSuccessMessage(event); - } else { - event.reply("辞書登録をキャンセルしました。").queue(); - } - }).setFinalAction(m -> { - try { - m.clearReactions().queue(); - m.delete().queue(); - } catch (PermissionException ignore) { - } - }).build().display(event.getMessageChannel()); - } else { - dictionary.updateDictionary(guildId, word, reading); - sendSuccessMessage(event); - } - } - - private void sendSuccessMessage(SlashCommandEvent event) { - EmbedBuilder builder = new EmbedBuilder() - .setColor(SUCCESS_COLOR) - .setTitle("単語を追加しました。") - .addField("単語", "```" + event.getOption("word").getAsString() + "```", false) - .addField("読み", "```" + event.getOption("reading").getAsString() + "```", false); - event.replyEmbeds(builder.build()).queue(); - } - - @Override - protected void execute(SlashCommandEvent event) { - String word = event.getOption("word").getAsString(); - String reading = event.getOption("reading").getAsString(); - - if (!isKatakana(reading)) { - event.reply("読み方はすべてカタカナで入力して下さい。").setEphemeral(true).queue(); - return; - } - - handleCommand(event, word, reading); - } -} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/DlWordCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/DlWordCmd.java deleted file mode 100644 index b9fbb72..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/DlWordCmd.java +++ /dev/null @@ -1,98 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.dictionary; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import org.slf4j.Logger; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import static org.slf4j.LoggerFactory.getLogger; - -public class DlWordCmd extends SlashCommand { - private final Bot bot; - private final Logger log = getLogger(getClass()); - - public DlWordCmd(Bot bot) { - this.bot = bot; - this.name = "wddl"; - this.help = "辞書に登録されている単語を削除します。"; - this.category = new Category("辞書"); - - List options = new ArrayList<>(); - options.add(new OptionData(OptionType.STRING, "word", "単語", true)); - - this.options = options; - } - - @Override - protected void execute(SlashCommandEvent event) { - HashMap words = bot.getDictionary().getWords(event.getGuild().getIdLong()); - - String args = event.getOption("word").getAsString(); - - if (!words.containsKey(args)) { - event.reply(args + "は、辞書に登録されていません。").queue(); - return; - } - - boolean result = bot.getDictionary().deleteDictionary(event.getGuild().getIdLong(), args); - - if (result) { - event.reply(String.format("単語(%s)を削除しました。", args)).queue(); - } else { - event.reply("削除中に問題が発生しました。").setEphemeral(true).queue(); - } - } - - @Override - protected void execute(CommandEvent event) { - if (event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty()) { - EmbedBuilder ebuilder = new EmbedBuilder() - .setTitle("dlwordコマンド") - .addField("使用方法:", name + " <単語>", false) - .addField("説明:", help, false); - event.reply(ebuilder.build()); - return; - } - - HashMap words = bot.getDictionary().getWords(event.getGuild().getIdLong()); - - String args = event.getArgs(); - - if (!words.containsKey(args)) { - event.reply(args + "は、辞書に登録されていません。"); - return; - } - - boolean result = bot.getDictionary().deleteDictionary(event.getGuild().getIdLong(), args); - - if (result) { - event.reply(String.format("単語(%s)を削除しました。", args)); - } else { - event.reply("削除中に問題が発生しました。"); - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/WordListCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/WordListCmd.java deleted file mode 100644 index 5fcfa53..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/dictionary/WordListCmd.java +++ /dev/null @@ -1,105 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.dictionary; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import com.jagrosh.jdautilities.menu.Paginator; -import dev.cosgy.TextToSpeak.Bot; -import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.exceptions.PermissionException; - -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -public class WordListCmd extends SlashCommand { - private final Bot bot; - private final Paginator.Builder builder; - - public WordListCmd(Bot bot) { - this.bot = bot; - this.name = "wdls"; - this.help = "辞書に登録してある単語をリストアップします。"; - this.category = new Category("辞書"); - this.botPermissions = new Permission[]{Permission.MESSAGE_ADD_REACTION, Permission.MESSAGE_EMBED_LINKS}; - builder = new Paginator.Builder() - .setColumns(1) - .setFinalAction(m -> { - try { - m.clearReactions().queue(); - } catch (PermissionException ignore) { - } - }) - .setItemsPerPage(10) - .waitOnSinglePage(false) - .useNumberedItems(true) - .showPageNumbers(true) - .wrapPageEnds(true) - .setEventWaiter(bot.getWaiter()) - .setTimeout(1, TimeUnit.MINUTES); - - } - - @Override - protected void execute(SlashCommandEvent event) { - event.reply("単語一覧を表示します。").queue(m -> { - List wordList = bot.getDictionary().getWords(event.getGuild().getIdLong()) - .entrySet().stream() - .map(entry -> entry.getKey() + "-" + entry.getValue()) - .collect(Collectors.toList()); - - if (wordList.isEmpty()) { - m.editOriginal("単語が登録されていません。").queue(); - return; - } - - m.deleteOriginal().queue(); - builder.setText("単語一覧") - .setItems(wordList.toArray(new String[0])) - .setUsers(event.getUser()) - .setColor(event.getGuild().getSelfMember().getColor()); - builder.build().paginate(event.getChannel(), 1); - }); - } - - @Override - protected void execute(CommandEvent event) { - int pagenum = 1; - try { - pagenum = Integer.parseInt(event.getArgs()); - } catch (NumberFormatException ignore) { - } - - List wordList = bot.getDictionary().getWords(event.getGuild().getIdLong()) - .entrySet().stream() - .map(entry -> entry.getKey() + "-" + entry.getValue()) - .collect(Collectors.toList()); - - if (wordList.isEmpty()) { - event.reply("単語が登録されていません。"); - return; - } - - builder.setText("単語一覧") - .setItems(wordList.toArray(new String[0])) - .setUsers(event.getAuthor()) - .setColor(event.getSelfMember().getColor()); - builder.build().paginate(event.getChannel(), pagenum); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/AboutCommand.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/AboutCommand.java deleted file mode 100644 index b47cb34..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/AboutCommand.java +++ /dev/null @@ -1,145 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandClient; -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import com.jagrosh.jdautilities.commons.JDAUtilitiesInfo; -import com.jagrosh.jdautilities.doc.standard.CommandInfo; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.JDAInfo; -import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.ApplicationInfo; -import net.dv8tion.jda.api.entities.channel.ChannelType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.awt.*; -import java.util.Objects; - -/** - * @author Kosugi_kun - */ -@CommandInfo( - name = "About", - description = "ボットに関する情報を表示します" -) - -public class AboutCommand extends SlashCommand { - private final Color color; - private final String description; - private final Permission[] perms; - private boolean IS_AUTHOR = true; - private String REPLACEMENT_ICON = "+"; - private String oauthLink; - - public AboutCommand(Color color, String description, Permission... perms) { - this.color = color; - this.description = description; - this.name = "about"; - this.help = "ボットに関する情報を表示します"; - this.guildOnly = false; - this.perms = perms; - this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS}; - } - - public void setIsAuthor(boolean value) { - this.IS_AUTHOR = value; - } - - public void setReplacementCharacter(String value) { - this.REPLACEMENT_ICON = value; - } - - @Override - protected void execute(SlashCommandEvent event) { - if (oauthLink == null) { - try { - ApplicationInfo info = event.getJDA().retrieveApplicationInfo().complete(); - oauthLink = info.isBotPublic() ? info.getInviteUrl(0L, perms) : ""; - } catch (Exception e) { - Logger log = LoggerFactory.getLogger("OAuth2"); - log.error("招待リンクを生成できませんでした ", e); - oauthLink = ""; - } - } - EmbedBuilder builder = new EmbedBuilder(); - builder.setColor(event.isFromType(ChannelType.TEXT) ? event.getGuild().getSelfMember().getColor() : color); - builder.setAuthor(event.getJDA().getSelfUser().getName() + "について!", null, event.getJDA().getSelfUser().getAvatarUrl()); - String CosgyOwner = "Cosgy Devが運営、開発をしています。"; - String author = event.getJDA().getUserById(event.getClient().getOwnerId()) == null ? "<@" + event.getClient().getOwnerId() + ">" - : Objects.requireNonNull(event.getJDA().getUserById(event.getClient().getOwnerId())).getName(); - StringBuilder descr = new StringBuilder().append("こんにちは! **").append(event.getJDA().getSelfUser().getName()).append("**です。 ") - .append(description).append("は、").append(JDAUtilitiesInfo.AUTHOR + "の[コマンド拡張](" + JDAUtilitiesInfo.GITHUB + ") (") - .append(JDAUtilitiesInfo.VERSION).append(")と[JDAライブラリ](https://github.com/DV8FromTheWorld/JDA) (") - .append(JDAInfo.VERSION).append(")を使用しており、").append((IS_AUTHOR ? CosgyOwner : author + "が所有しています。")) - .append(event.getJDA().getSelfUser().getName()).append("についての質問などは[Cosgy Dev公式チャンネル](https://discord.gg/RBpkHxf)へお願いします。") - .append("\nこのボットの使用方法は`").append(event.getClient().getTextualPrefix()).append(event.getClient().getHelpWord()) - .append("`で確認することができます。"); - getMessage(builder, descr, event.getJDA(), event.getClient()); - event.replyEmbeds(builder.build()).queue(); - } - - private void getMessage(EmbedBuilder builder, StringBuilder descr, JDA jda, CommandClient client) { - builder.setDescription(descr); - - if (jda.getShardInfo().getShardTotal() == 1) { - builder.addField("ステータス", jda.getGuilds().size() + " サーバー\n1 シャード", true); - builder.addField("ユーザー", jda.getUsers().size() + " ユニーク\n" + jda.getGuilds().stream().mapToInt(g -> g.getMembers().size()).sum() + " 合計", true); - builder.addField("チャンネル", jda.getTextChannels().size() + " テキスト\n" + jda.getVoiceChannels().size() + " ボイス", true); - } else { - builder.addField("ステータス", (client).getTotalGuilds() + " サーバー\nシャード " + (jda.getShardInfo().getShardId() + 1) - + "/" + jda.getShardInfo().getShardTotal(), true); - builder.addField("", jda.getUsers().size() + " ユーザーのシャード\n" + jda.getGuilds().size() + " サーバー", true); - builder.addField("", jda.getTextChannels().size() + " テキストチャンネル\n" + jda.getVoiceChannels().size() + " ボイスチャンネル", true); - } - builder.setFooter("再起動が行われた時間", "https://www.cosgy.dev/wp-content/uploads/2020/03/restart.jpg"); - builder.setTimestamp(client.getStartTime()); - } - - @Override - protected void execute(CommandEvent event) { - if (oauthLink == null) { - try { - ApplicationInfo info = event.getJDA().retrieveApplicationInfo().complete(); - oauthLink = info.isBotPublic() ? info.getInviteUrl(0L, perms) : ""; - } catch (Exception e) { - Logger log = LoggerFactory.getLogger("OAuth2"); - log.error("招待リンクを生成できませんでした ", e); - oauthLink = ""; - } - } - EmbedBuilder builder = new EmbedBuilder(); - builder.setColor(event.isFromType(ChannelType.TEXT) ? event.getGuild().getSelfMember().getColor() : color); - builder.setAuthor(event.getSelfUser().getName() + "について!", null, event.getSelfUser().getAvatarUrl()); - String CosgyOwner = "Cosgy Devが運営、開発をしています。"; - String author = event.getJDA().getUserById(event.getClient().getOwnerId()) == null ? "<@" + event.getClient().getOwnerId() + ">" - : Objects.requireNonNull(event.getJDA().getUserById(event.getClient().getOwnerId())).getName(); - StringBuilder descr = new StringBuilder().append("こんにちは! **").append(event.getSelfUser().getName()).append("**です。 ") - .append(description).append("は、").append(JDAUtilitiesInfo.AUTHOR + "の[コマンド拡張](" + JDAUtilitiesInfo.GITHUB + ") (") - .append(JDAUtilitiesInfo.VERSION).append(")と[JDAライブラリ](https://github.com/DV8FromTheWorld/JDA) (") - .append(JDAInfo.VERSION).append(")を使用しており、").append((IS_AUTHOR ? CosgyOwner : author + "が所有しています。")) - .append(event.getSelfUser().getName()).append("についての質問などは[Cosgy Dev公式チャンネル](https://discord.gg/RBpkHxf)へお願いします。") - .append("\nこのボットの使用方法は`").append(event.getClient().getTextualPrefix()).append(event.getClient().getHelpWord()) - .append("`で確認することができます。"); - getMessage(builder, descr, event.getJDA(), event.getClient()); - event.reply(builder.build()); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/ByeCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/ByeCmd.java deleted file mode 100644 index f903010..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/ByeCmd.java +++ /dev/null @@ -1,74 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.audio.AudioHandler; -import net.dv8tion.jda.api.EmbedBuilder; - -import java.awt.*; -import java.io.IOException; - -public class ByeCmd extends SlashCommand { - protected final Bot bot; - - public ByeCmd(Bot bot) { - this.bot = bot; - this.name = "bye"; - this.help = "ボイスチャンネルから退出します。"; - this.guildOnly = true; - } - - @Override - protected void execute(SlashCommandEvent event) { - AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler(); - handler.stopAndClear(); - try { - bot.getVoiceCreation().clearGuildFolder(event.getGuild()); - } catch (IOException e) { - throw new RuntimeException(e); - } - event.getGuild().getAudioManager().closeAudioConnection(); - - EmbedBuilder builder = new EmbedBuilder(); - builder.setColor(new Color(180, 76, 151)); - builder.setTitle("VCから切断"); - builder.setDescription("ボイスチャンネルから切断しました。"); - event.replyEmbeds(builder.build()).queue(); - } - - @Override - protected void execute(CommandEvent event) { - AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler(); - handler.stopAndClear(); - try { - bot.getVoiceCreation().clearGuildFolder(event.getGuild()); - } catch (IOException e) { - throw new RuntimeException(e); - } - event.getGuild().getAudioManager().closeAudioConnection(); - - EmbedBuilder builder = new EmbedBuilder(); - builder.setColor(new Color(180, 76, 151)); - builder.setTitle("VCから切断"); - builder.setDescription("ボイスチャンネルから切断しました。"); - event.reply(builder.build()); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/HelpCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/HelpCmd.java deleted file mode 100644 index 8dba584..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/HelpCmd.java +++ /dev/null @@ -1,100 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.Command; -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.channel.ChannelType; - -import java.awt.*; -import java.util.List; -import java.util.Objects; - -public class HelpCmd extends SlashCommand { - public Bot bot; - - public HelpCmd(Bot bot) { - this.bot = bot; - this.name = "help"; - this.help = "コマンド一覧を表示します。"; - } - - @Override - protected void execute(SlashCommandEvent event) { - EmbedBuilder eBuilder = new EmbedBuilder(); - eBuilder.setTitle("**" + event.getJDA().getSelfUser().getName() + "** コマンド一覧"); - eBuilder.setColor(new Color(245, 229, 107)); - - StringBuilder builder = new StringBuilder(); - Category category = null; - List commands = event.getClient().getSlashCommands(); - for (SlashCommand command : commands) { - if (!command.isHidden() && (!command.isOwnerCommand() || event.getMember().isOwner())) { - if (!Objects.equals(category, command.getCategory())) { - category = command.getCategory(); - builder.append("\n\n __").append(category == null ? "カテゴリなし" : category.getName()).append("__:\n"); - } - builder.append("\n`").append("/").append(command.getName()) - .append(command.getArguments() == null ? "`" : " " + command.getArguments() + "`") - .append(" - ").append(command.getHelp()); - } - } - if (event.getClient().getServerInvite() != null) - builder.append("\n\nさらにヘルプが必要な場合は、公式サーバーに参加することもできます: ").append(client.getServerInvite()); - - eBuilder.setDescription(builder); - - if (bot.getConfig().getHelpToDm()) { - event.getUser().openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(eBuilder.build())).queue(); - } else { - event.replyEmbeds(eBuilder.build()).queue(); - } - } - - public void execute(CommandEvent event) { - StringBuilder builder = new StringBuilder("**" + event.getJDA().getSelfUser().getName() + "** コマンド一覧:\n"); - Category category = null; - List commands = event.getClient().getCommands(); - for (Command command : commands) { - if (!command.isHidden() && (!command.isOwnerCommand() || event.isOwner())) { - if (!Objects.equals(category, command.getCategory())) { - category = command.getCategory(); - builder.append("\n\n __").append(category == null ? "カテゴリなし" : category.getName()).append("__:\n"); - } - builder.append("\n`").append(event.getClient().getTextualPrefix()).append(event.getClient().getPrefix() == null ? " " : "").append(command.getName()) - .append(command.getArguments() == null ? "`" : " " + command.getArguments() + "`") - .append(" - ").append(command.getHelp()); - } - } - if (event.getClient().getServerInvite() != null) - builder.append("\n\nさらにヘルプが必要な場合は、公式サーバーに参加することもできます: ").append(event.getClient().getServerInvite()); - - if (bot.getConfig().getHelpToDm()) { - event.replyInDm(builder.toString(), unused -> - { - if (event.isFromType(ChannelType.TEXT)) - event.reactSuccess(); - }, t -> event.replyWarning("ダイレクトメッセージをブロックしているため、ヘルプを送信できません。")); - } else { - event.reply(builder.toString()); - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/JoinCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/JoinCmd.java deleted file mode 100644 index 49098b2..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/JoinCmd.java +++ /dev/null @@ -1,114 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.settings.Settings; -import dev.cosgy.TextToSpeak.utils.ReadChannel; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.GuildVoiceState; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; -import net.dv8tion.jda.api.exceptions.PermissionException; - -import java.awt.*; - -public class JoinCmd extends SlashCommand { - protected Bot bot; - - public JoinCmd(Bot bot) { - this.bot = bot; - this.name = "join"; - this.help = "ボイスチャンネルに参加します。"; - this.guildOnly = true; - } - - @Override - protected void execute(SlashCommandEvent event) { - event.deferReply().queue(); - Settings settings = event.getClient().getSettingsFor(event.getGuild()); - TextChannel channel = settings.getTextChannel(event.getGuild()); - // VoiceChannel voiceChannel = settings.getVoiceChannel(event.getGuild()); - bot.getPlayerManager().setUpHandler(event.getGuild()); - - GuildVoiceState userState = event.getMember().getVoiceState(); - EmbedBuilder builder = new EmbedBuilder(); - - builder.setColor(new Color(76, 108, 179)); - builder.setTitle("VCに接続"); - - if (!userState.inAudioChannel() || userState.isDeafened()) { - builder.setDescription(String.format("このコマンドを使用するには、%sに参加している必要があります。", "音声チャンネル")); - event.replyEmbeds(builder.build()).queue(); - return; - } - - builder.addField("読み上げ対象", "<#" + event.getChannel().getId() + ">", false); - - try { - event.getGuild().getAudioManager().openAudioConnection(userState.getChannel()); - builder.addField("ボイスチャンネル", String.format("**%s**", userState.getChannel().getName()), false); - builder.setDescription("ボイスチャンネルへの接続に成功しました。"); - - event.getHook().sendMessageEmbeds(builder.build()).queue(); - ReadChannel.setChannel(event.getGuild().getIdLong(), event.getTextChannel().getIdLong()); - } catch (PermissionException ex) { - builder.appendDescription(event.getClient().getError() + String.format("**%s**に接続できません!", userState.getChannel().getName())); - builder.addField("ボイスチャンネル", event.getClient().getError() + String.format("**%s**に接続できません!", - userState.getChannel().getName()), false); - event.getHook().sendMessageEmbeds(builder.build()).queue(); - } - } - - @Override - protected void execute(CommandEvent event) { - Settings settings = event.getClient().getSettingsFor(event.getGuild()); - TextChannel channel = settings.getTextChannel(event.getGuild()); - bot.getPlayerManager().setUpHandler(event.getGuild()); - - GuildVoiceState userState = event.getMember().getVoiceState(); - - EmbedBuilder builder = new EmbedBuilder(); - builder.setColor(new Color(76, 108, 179)); - builder.setTitle("VCに接続"); - - if (!userState.inAudioChannel()) { - builder.setDescription("このコマンドを使用するには、ボイスチャンネルに参加している必要があります。"); - event.reply(builder.build()); - return; - } - if (channel == null) { - builder.addField("読み上げ対象", event.getChannel().getName(), true); - } else { - builder.addField("読み上げ対象", channel.getName(), true); - } - try { - event.getGuild().getAudioManager().openAudioConnection(userState.getChannel()); - builder.addField("ボイスチャンネル", String.format("**%s**", userState.getChannel().getName()), false); - builder.setDescription("ボイスチャンネルへの接続に成功しました。"); - - - event.reply(builder.build()); - ReadChannel.setChannel(event.getGuild().getIdLong(), event.getTextChannel().getIdLong()); - } catch (PermissionException ex) { - builder.setDescription("ボイスチャンネルへの接続に失敗しました。"); - event.reply(builder.build()); - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetIntonationCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetIntonationCmd.java deleted file mode 100644 index e143f3e..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetIntonationCmd.java +++ /dev/null @@ -1,109 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.settings.UserSettings; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -public class SetIntonationCmd extends SlashCommand { - protected Bot bot; - - public SetIntonationCmd(Bot bot) { - this.bot = bot; - this.name = "setinto"; - this.help = "F0系列内変動の重みの設定を変更します。"; - this.guildOnly = false; - this.category = new Category("設定"); - - List options = new ArrayList<>(); - options.add(new OptionData(OptionType.STRING, "value", "0.1~100.0", true)); - - this.options = options; - } - - @Override - protected void execute(SlashCommandEvent event) { - String args = event.getOption("value").getAsString(); - boolean result; - BigDecimal bd = null; - try { - bd = new BigDecimal(args); - result = true; - } catch (NumberFormatException e) { - result = false; - } - if (!result) { - event.reply("数値を設定して下さい。").queue(); - return; - } - BigDecimal min = new BigDecimal("0.0"); - BigDecimal max = new BigDecimal("100.0"); - - if (!(min.compareTo(bd) < 0 && max.compareTo(bd) > 0)) { - event.reply("有効な数値を設定して下さい。0.1~100.0").queue(); - return; - } - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getUser().getIdLong()); - settings.setIntonation(bd.floatValue()); - event.reply("F0系列内変動の重みを" + bd + "に設定しました。").queue(); - } - - @Override - protected void execute(CommandEvent event) { - if (event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty()) { - EmbedBuilder ebuilder = new EmbedBuilder() - .setTitle("setintoコマンド") - .addField("使用方法:", name + " <数値(0.0~)>", false) - .addField("説明:", "F0系列内変動の重みを変更します。F0系列内変動の重みは、0.0以上の数値で設定して下さい。", false); - event.reply(ebuilder.build()); - return; - } - String args = event.getArgs(); - boolean result; - BigDecimal bd = null; - try { - bd = new BigDecimal(args); - result = true; - } catch (NumberFormatException e) { - result = false; - } - if (!result) { - event.reply("数値を設定して下さい。"); - return; - } - BigDecimal min = new BigDecimal("0.0"); - BigDecimal max = new BigDecimal("100.0"); - - if (!(min.compareTo(bd) < 0 && max.compareTo(bd) > 0)) { - event.reply("有効な数値を設定して下さい。0.1~100.0"); - return; - } - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getAuthor().getIdLong()); - settings.setIntonation(bd.floatValue()); - event.reply("F0系列内変動の重みを" + bd + "に設定しました。"); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetSpeedCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetSpeedCmd.java deleted file mode 100644 index 2c39686..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetSpeedCmd.java +++ /dev/null @@ -1,111 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.settings.UserSettings; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -public class SetSpeedCmd extends SlashCommand { - protected Bot bot; - - public SetSpeedCmd(Bot bot) { - this.guildOnly = false; - this.bot = bot; - this.name = "setspeed"; - this.help = "読み上げ速度の設定を変更します。"; - this.category = new Category("設定"); - - List options = new ArrayList<>(); - options.add(new OptionData(OptionType.STRING, "value", "0.1~100.0", true)); - - this.options = options; - } - - @Override - protected void execute(SlashCommandEvent event) { - String args = event.getOption("value").getAsString(); - boolean result; - BigDecimal bd = null; - try { - bd = new BigDecimal(args); - result = true; - } catch (NumberFormatException e) { - result = false; - } - if (!result) { - event.reply("数値を設定して下さい。").queue(); - return; - } - - BigDecimal min = new BigDecimal("0.0"); - BigDecimal max = new BigDecimal("100.0"); - - if (!(min.compareTo(bd) < 0 && max.compareTo(bd) > 0)) { - event.reply("有効な数値を設定して下さい。0.1~100.0").queue(); - return; - } - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getUser().getIdLong()); - settings.setSpeed(bd.floatValue()); - event.reply("速度を" + bd + "に設定しました。").queue(); - } - - @Override - protected void execute(CommandEvent event) { - if (event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty()) { - EmbedBuilder ebuilder = new EmbedBuilder() - .setTitle("setspeedコマンド") - .addField("使用方法:", name + " <数値(0.0~)>", false) - .addField("説明:", "読み上げの速度を設定します。読み上げ速度は、0.0以上の数値で設定して下さい。", false); - event.reply(ebuilder.build()); - return; - } - String args = event.getArgs(); - boolean result; - BigDecimal bd = null; - try { - bd = new BigDecimal(args); - result = true; - } catch (NumberFormatException e) { - result = false; - } - if (!result) { - event.reply("数値を設定して下さい。"); - return; - } - - BigDecimal min = new BigDecimal("0.0"); - BigDecimal max = new BigDecimal("100.0"); - - if (!(min.compareTo(bd) < 0 && max.compareTo(bd) > 0)) { - event.reply("有効な数値を設定して下さい。0.1~100.0"); - return; - } - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getAuthor().getIdLong()); - settings.setSpeed(bd.floatValue()); - event.reply("速度を" + bd + "に設定しました。"); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceCmd.java deleted file mode 100644 index 3be10f8..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceCmd.java +++ /dev/null @@ -1,123 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.settings.UserSettings; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.Command; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class SetVoiceCmd extends SlashCommand { - protected Bot bot; - String[] voices; - - public SetVoiceCmd(Bot bot) { - this.bot = bot; - this.name = "setvoice"; - this.help = "声の種類を変更することができます。"; - this.guildOnly = false; - this.category = new Category("設定"); - - List options = new ArrayList<>(); - options.add(new OptionData(OptionType.STRING, "name", "声データの名前", true, true)); - - this.options = options; - voices = bot.getVoiceCreation().getVoices().toArray(new String[0]); - } - - @Override - protected void execute(SlashCommandEvent event) { - if (event.getOption("name") == null) { - EmbedBuilder ebuilder = new EmbedBuilder() - .setTitle("setvoiceコマンド") - .addField("声データ一覧:", Arrays.toString(voices), false) - .addField("使用方法:", name + " <声データの名前>", false); - event.replyEmbeds(ebuilder.build()).queue(); - return; - } - String viceName = event.getOption("name").getAsString(); - - if (isValidVoice(viceName)) { - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getUser().getIdLong()); - settings.setVoice(viceName); - event.reply("声データを`" + viceName + "`に設定しました。").queue(); - } else { - event.reply("有効な声データを選択して下さい。").queue(); - } - } - - @Override - protected void execute(CommandEvent event) { - ArrayList voices = bot.getVoiceCreation().getVoices(); - if (event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty()) { - EmbedBuilder ebuilder = new EmbedBuilder() - .setTitle("setvoiceコマンド") - .addField("声データ一覧:", voices.toString(), false) - .addField("使用方法:", name + " <声データの名前>", false); - event.reply(ebuilder.build()); - return; - } - String args = event.getArgs(); - if (voices.contains(args)) { - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getAuthor().getIdLong()); - settings.setVoice(args); - event.reply("声データを`" + args + "`に設定しました。"); - } else { - event.reply("有効な声データを選択して下さい。"); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event) { - if (event.getName().equals("setvoice") && event.getFocusedOption().getName().equals("name")) { - List options = Stream.of(voices) - .filter(word -> word.startsWith(event.getFocusedOption().getValue())) // only display words that start with the user's current input - .map(word -> new Command.Choice(word, word)) // map the words to choices - .collect(Collectors.toList()); - event.replyChoices(options).queue(); - } - super.onAutoComplete(event); - } - - /** - * ユーザーが入力した声の名前が有効かを確認 - * - * @param voice ユーザーが入力した声の名前 - * @return 名前が有効の場合は true 無効な場合は false - */ - private boolean isValidVoice(String voice) { - for (String v : voices) { - if (Objects.equals(v, voice)) { - return true; - } - } - return false; - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceQualityA.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceQualityA.java deleted file mode 100644 index c62a5be..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceQualityA.java +++ /dev/null @@ -1,108 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.settings.UserSettings; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -public class SetVoiceQualityA extends SlashCommand { - protected Bot bot; - - public SetVoiceQualityA(Bot bot) { - this.bot = bot; - this.name = "setqa"; - this.help = "オールパス値の設定を変更します。"; - this.guildOnly = false; - this.category = new Category("設定"); - - List options = new ArrayList<>(); - options.add(new OptionData(OptionType.STRING, "value", "0.1~1.0", true)); - - this.options = options; - } - - @Override - protected void execute(SlashCommandEvent event) { - String args = event.getOption("value").getAsString(); - boolean result; - float value = 0.0f; - BigDecimal bd = null; - try { - //value = Float.parseFloat(args); - bd = new BigDecimal(args); - result = true; - } catch (NumberFormatException e) { - result = false; - } - if (!result) { - event.reply("数値を設定して下さい。").queue(); - return; - } - - BigDecimal min = new BigDecimal("0.0"); - BigDecimal max = new BigDecimal("1.0"); - - //if(!(0.1f <= value && value <= 1.0f)){ - if (!(min.compareTo(bd) < 0 && max.compareTo(bd) > 0)) { - event.reply("有効な数値を設定して下さい。0.1~1.0").queue(); - return; - } - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getUser().getIdLong()); - settings.setVoiceQualityA(bd.floatValue()); - event.reply("オールパス値を" + bd + "に設定しました。").queue(); - } - - @Override - protected void execute(CommandEvent event) { - String args = event.getArgs(); - boolean result; - float value = 0.0f; - BigDecimal bd = null; - try { - //value = Float.parseFloat(args); - bd = new BigDecimal(args); - result = true; - } catch (NumberFormatException e) { - result = false; - } - if (!result) { - event.reply("数値を設定して下さい。"); - return; - } - - BigDecimal min = new BigDecimal("0.0"); - BigDecimal max = new BigDecimal("1.0"); - - //if(!(0.1f <= value && value <= 1.0f)){ - if (!(min.compareTo(bd) < 0 && max.compareTo(bd) > 0)) { - event.reply("有効な数値を設定して下さい。0.1~1.0"); - return; - } - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getAuthor().getIdLong()); - settings.setVoiceQualityA(bd.floatValue()); - event.reply("オールパス値を" + bd + "に設定しました。"); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceQualityFm.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceQualityFm.java deleted file mode 100644 index 637ea6d..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SetVoiceQualityFm.java +++ /dev/null @@ -1,117 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.settings.UserSettings; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -public class SetVoiceQualityFm extends SlashCommand { - protected Bot bot; - - public SetVoiceQualityFm(Bot bot) { - this.bot = bot; - this.name = "setqfm"; - this.help = "追加ハーフトーンの設定を変更します。"; - this.guildOnly = false; - this.category = new Category("設定"); - - List options = new ArrayList<>(); - options.add(new OptionData(OptionType.STRING, "value", "0.1~100.0", true)); - - this.options = options; - } - - @Override - protected void execute(SlashCommandEvent event) { - String args = event.getOption("value").getAsString(); - boolean result; - float value = 0.0f; - BigDecimal bd = null; - try { - //value = Float.parseFloat(args); - bd = new BigDecimal(args); - result = true; - } catch (NumberFormatException e) { - result = false; - } - if (!result) { - event.reply("数値を設定して下さい。").queue(); - return; - } - - BigDecimal min = new BigDecimal("0.0"); - BigDecimal max = new BigDecimal("100.0"); - - //if(!(0.1f <= value && value <= 100.0f)){ - if (!(min.compareTo(bd) < 0 && max.compareTo(bd) > 0)) { - event.reply("有効な数値を設定して下さい。0.1~100.0").queue(); - return; - } - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getUser().getIdLong()); - settings.setVoiceQualityFm(bd.floatValue()); - event.reply("追加ハーフトーンを" + bd + "に設定しました。").queue(); - } - - @Override - protected void execute(CommandEvent event) { - if (event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty()) { - EmbedBuilder ebuilder = new EmbedBuilder() - .setTitle("setqfmコマンド") - .addField("使用方法:", name + " <数値(0.0~)>", false) - .addField("説明:", "追加ハーフトーンの設定を変更します。", false); - event.reply(ebuilder.build()); - return; - } - String args = event.getArgs(); - boolean result; - float value = 0.0f; - BigDecimal bd = null; - try { - //value = Float.parseFloat(args); - bd = new BigDecimal(args); - result = true; - } catch (NumberFormatException e) { - result = false; - } - if (!result) { - event.reply("数値を設定して下さい。"); - return; - } - - BigDecimal min = new BigDecimal("0.0"); - BigDecimal max = new BigDecimal("100.0"); - - //if(!(0.1f <= value && value <= 100.0f)){ - if (!(min.compareTo(bd) < 0 && max.compareTo(bd) > 0)) { - event.reply("有効な数値を設定して下さい。0.1~100.0"); - return; - } - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getAuthor().getIdLong()); - settings.setVoiceQualityFm(bd.floatValue()); - event.reply("追加ハーフトーンを" + bd + "に設定しました。"); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SettingsCmd.java b/src/main/java/dev/cosgy/TextToSpeak/commands/general/SettingsCmd.java deleted file mode 100644 index 46c66b7..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/general/SettingsCmd.java +++ /dev/null @@ -1,68 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.commands.general; - -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommand; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.settings.UserSettings; -import net.dv8tion.jda.api.EmbedBuilder; - -import java.awt.*; - -public class SettingsCmd extends SlashCommand { - protected Bot bot; - - public SettingsCmd(Bot bot) { - this.bot = bot; - this.guildOnly = false; - this.name = "settings"; - this.help = "現在の設定を確認します。"; - this.category = new Category("設定"); - } - - @Override - protected void execute(SlashCommandEvent event) { - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getUser().getIdLong()); - - EmbedBuilder ebuilder = new EmbedBuilder() - .setColor(Color.orange) - .setTitle(event.getUser().getName() + "の設定") - .addField("声:", settings.getVoice(), false) - .addField("読み上げ速度:", String.valueOf(settings.getSpeed()), false) - .addField("F0系列内変動の重み:", String.valueOf(settings.getIntonation()), false) - .addField("オールパス値:", String.valueOf(settings.getVoiceQualityA()), false) - .addField("追加ハーフトーン:", String.valueOf(settings.getVoiceQualityFm()), false); - event.replyEmbeds(ebuilder.build()).queue(); - } - - @Override - protected void execute(CommandEvent event) { - UserSettings settings = bot.getUserSettingsManager().getSettings(event.getAuthor().getIdLong()); - - EmbedBuilder ebuilder = new EmbedBuilder() - .setColor(Color.orange) - .setTitle(event.getAuthor().getName() + "の設定") - .addField("声:", settings.getVoice(), false) - .addField("速度:", String.valueOf(settings.getSpeed()), false) - .addField("抑揚:", String.valueOf(settings.getIntonation()), false) - .addField("声質a:", String.valueOf(settings.getVoiceQualityA()), false) - .addField("声質fm:", String.valueOf(settings.getVoiceQualityFm()), false); - event.reply(ebuilder.build()); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/entities/Prompt.java b/src/main/java/dev/cosgy/TextToSpeak/entities/Prompt.java deleted file mode 100644 index c52f5d5..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/entities/Prompt.java +++ /dev/null @@ -1,118 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.entities; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.*; -import java.util.Scanner; - -public class Prompt { - private final String title; - private final String noguiMessage; - - private boolean nogui; - private Scanner scanner; - - public Prompt(String title) { - this(title, null); - } - - public Prompt(String title, String noguiMessage) { - this(title, noguiMessage, "true".equalsIgnoreCase(System.getProperty("nogui"))); - } - - public Prompt(String title, String noguiMessage, boolean nogui) { - this.title = title; - this.noguiMessage = noguiMessage == null ? "noguiモードに切り替えます。 -nogui=trueフラグを含めることで、手動でnoguiモードで起動できます。" : noguiMessage; - this.nogui = nogui; - } - - public boolean isNoGUI() { - return nogui; - } - - public void alert(Level level, String context, String message) { - if (nogui) { - Logger log = LoggerFactory.getLogger(context); - switch (level) { - case WARNING: - log.warn(message); - break; - case ERROR: - log.error(message); - break; - case INFO: - default: - log.info(message); - break; - } - } else { - try { - int option = 0; - switch (level) { - case INFO: - option = JOptionPane.INFORMATION_MESSAGE; - break; - case WARNING: - option = JOptionPane.WARNING_MESSAGE; - break; - case ERROR: - break; - default: - option = JOptionPane.PLAIN_MESSAGE; - break; - } - JOptionPane.showMessageDialog(null, "

" + message, title, option); - } catch (Exception e) { - nogui = true; - alert(Level.WARNING, context, noguiMessage); - alert(level, context, message); - } - } - } - - public String prompt(String content) { - if (nogui) { - if (scanner == null) - scanner = new Scanner(System.in); - try { - System.out.println(content); - if (scanner.hasNextLine()) - return scanner.nextLine(); - return null; - } catch (Exception e) { - alert(Level.ERROR, title, "コマンドラインから入力を読み込めません。"); - e.printStackTrace(); - return null; - } - } else { - try { - return JOptionPane.showInputDialog(null, content, title, JOptionPane.QUESTION_MESSAGE); - } catch (Exception e) { - nogui = true; - alert(Level.WARNING, title, noguiMessage); - return prompt(content); - } - } - } - - public enum Level { - INFO, WARNING, ERROR - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/gui/GUI.java b/src/main/java/dev/cosgy/TextToSpeak/gui/GUI.java deleted file mode 100644 index 086a58e..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/gui/GUI.java +++ /dev/null @@ -1,76 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.gui; - -import dev.cosgy.TextToSpeak.Bot; - -import javax.swing.*; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; - -/** - * @author Kosugi_kun - */ -public class GUI extends JFrame { - private final ConsolePanel console; - private final Bot bot; - - public GUI(Bot bot) { - super(); - this.bot = bot; - console = new ConsolePanel(); - } - - public void init() { - setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - setTitle(bot.GetLang().getString("appName")); - JTabbedPane tabs = new JTabbedPane(); - tabs.add("コンソール", console); - getContentPane().add(tabs); - pack(); - setLocationRelativeTo(null); - setVisible(true); - addWindowListener(new WindowListener() { - @Override - public void windowOpened(WindowEvent e) { /* unused */ } - - @Override - public void windowClosing(WindowEvent e) { - try { - bot.shutdown(); - } catch (Exception ex) { - System.exit(0); - } - } - - @Override - public void windowClosed(WindowEvent e) { /* unused */ } - - @Override - public void windowIconified(WindowEvent e) { /* unused */ } - - @Override - public void windowDeiconified(WindowEvent e) { /* unused */ } - - @Override - public void windowActivated(WindowEvent e) { /* unused */ } - - @Override - public void windowDeactivated(WindowEvent e) { /* unused */ } - }); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/gui/TextAreaOutputStream.java b/src/main/java/dev/cosgy/TextToSpeak/gui/TextAreaOutputStream.java deleted file mode 100644 index 97bfbba..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/gui/TextAreaOutputStream.java +++ /dev/null @@ -1,152 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.gui; - -import javax.swing.*; -import java.awt.*; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * @author Kosugi_kun - */ -public class TextAreaOutputStream extends OutputStream { - private final byte[] oneByte; - private Appender appender; - - public TextAreaOutputStream(JTextArea txtara) { - this(txtara, 1000); - } - - public TextAreaOutputStream(JTextArea txtara, int maxlin) { - if (maxlin < 1) { - throw new IllegalArgumentException("TextAreaOutputStreamの最大行数は正数でなければなりません(value=" + maxlin + ")"); - } - oneByte = new byte[1]; - appender = new Appender(txtara, maxlin); - } - - static private String bytesToString(byte[] ba, int str, int len) { - try { - return new String(ba, str, len, System.getProperty("file.encoding")); - } catch (UnsupportedEncodingException thr) { - return new String(ba, str, len); - } - } - - public synchronized void clear() { - if (appender != null) { - appender.clear(); - } - } - - @Override - public synchronized void close() { - appender = null; - } - - @Override - public synchronized void flush() { - /* empty */ - } - - @Override - public synchronized void write(int val) { - oneByte[0] = (byte) val; - write(oneByte, 0, 1); - } - - @Override - public synchronized void write(byte[] ba) { - write(ba, 0, ba.length); - } - - @Override - public synchronized void write(byte[] ba, int str, int len) { - if (appender != null) { - appender.append(bytesToString(ba, str, len)); - } - } - - static class Appender - implements Runnable { - static private final String EOL1 = "\n"; - static private final String EOL2 = System.getProperty("line.separator", EOL1); - - private final JTextArea textArea; - private final int maxLines; - private final LinkedList lengths; - private final List values; - - private int curLength = 0; - private boolean clear; - private boolean queue; - - Appender(JTextArea txtara, int maxlin) { - textArea = txtara; - maxLines = maxlin; - lengths = new LinkedList<>(); - values = new ArrayList<>(); - - clear = false; - queue = true; - } - - private synchronized void append(String val) { - values.add(val); - if (queue) { - queue = false; - EventQueue.invokeLater(this); - } - } - - private synchronized void clear() { - clear = true; - curLength = 0; - lengths.clear(); - values.clear(); - if (queue) { - queue = false; - EventQueue.invokeLater(this); - } - } - - @Override - public synchronized void run() { - if (clear) { - textArea.setText(""); - } - values.stream() - .peek((val) -> curLength += val.length()) - .peek((val) -> { - if (val.endsWith(EOL1) || val.endsWith(EOL2)) { - if (lengths.size() >= maxLines) { - textArea.replaceRange("", 0, lengths.removeFirst()); - } - lengths.addLast(curLength); - curLength = 0; - } - }).forEach(textArea::append); - values.clear(); - clear = false; - queue = true; - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/listeners/MessageListener.java b/src/main/java/dev/cosgy/TextToSpeak/listeners/MessageListener.java deleted file mode 100644 index ebb9f09..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/listeners/MessageListener.java +++ /dev/null @@ -1,161 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.listeners; - -import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.audio.AudioHandler; -import dev.cosgy.TextToSpeak.audio.QueuedTrack; -import dev.cosgy.TextToSpeak.audio.VoiceCreation; -import dev.cosgy.TextToSpeak.utils.ReadChannel; -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.entities.channel.ChannelType; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import net.dv8tion.jda.api.events.session.ReadyEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; - -import java.io.IOException; - -public class MessageListener extends ListenerAdapter { - private final Bot bot; - - public MessageListener(Bot bot) { - this.bot = bot; - } - - @Override - public void onMessageReceived(MessageReceivedEvent event) { - JDA jda = event.getJDA(); - long responseNumber = event.getResponseNumber(); - - //イベント固有の情報 - User author = event.getAuthor(); //メッセージを送信したユーザー - - Message message = event.getMessage(); //受信したメッセージ。 - MessageChannel channel = event.getChannel(); //メッセージが送信されたMessageChannel - - String msg = message.getContentDisplay(); - //人間が読める形式のメッセージが返されます。 クライアントに表示されるものと同様。 - - boolean isBot = author.isBot(); - //if(Arrays.asList(mentionedUsers).contains()) - //メッセージを送信したユーザーがBOTであるかどうかを判断。 - - if (event.isFromType(ChannelType.TEXT)) { - if (isBot) return; - Guild guild = event.getGuild(); - TextChannel textChannel = event.getGuildChannel().asTextChannel(); - TextChannel settingText = bot.getSettingsManager().getSettings(event.getGuild()).getTextChannel(event.getGuild()); - - if (!guild.getAudioManager().isConnected()) { - return; - } - - String prefix = bot.getConfig().getPrefix().equals("@mention") ? "@" + event.getJDA().getSelfUser().getName() + " " : bot.getConfig().getPrefix(); - - if (msg.startsWith(prefix)) { - return; - } - - if (textChannel != settingText) { - if (settingText == null) { - settingText = event.getGuild().getTextChannelById(ReadChannel.getChannel(event.getGuild().getIdLong())); - } - } - - // URLを置き換え - msg = msg.replaceAll("(http://|https://)[\\w.\\-/:#?=&;%~+]+", "ゆーあーるえる"); - - if (textChannel == settingText) { - - if (bot.getSettingsManager().getSettings(guild).isReadName()) { - msg = author.getName() + " " + msg; - } - - VoiceCreation vc = bot.getVoiceCreation(); - String file; - try { - file = vc.createVoice(guild, author, msg); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - - bot.getPlayerManager().loadItemOrdered(event.getGuild(), file, new ResultHandler(null, event, false)); - - //textChannel.sendMessage(author.getName() + "が、「"+ msg +"」と送信しました。").queue(); - } - } - } - - - @Override - public void onReady(ReadyEvent e) { - bot.readyJDA(); - } - - private static class ResultHandler implements AudioLoadResultHandler { - private final MessageReceivedEvent event; - - private ResultHandler(Message m, MessageReceivedEvent event, boolean ytsearch) { - this.event = event; - } - - private void loadSingle(AudioTrack track, AudioPlaylist playlist) { - AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler(); - handler.addTrack(new QueuedTrack(track, event.getAuthor())); - } - - private int loadPlaylist(AudioPlaylist playlist, AudioTrack exclude) { - int[] count = {0}; - playlist.getTracks().forEach((track) -> { - if (!track.equals(exclude)) { - AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler(); - handler.addTrack(new QueuedTrack(track, event.getAuthor())); - count[0]++; - } - }); - return count[0]; - } - - @Override - public void trackLoaded(AudioTrack track) { - loadSingle(track, null); - } - - @Override - public void playlistLoaded(AudioPlaylist playlist) { - - } - - @Override - public void noMatches() { - } - - @Override - public void loadFailed(FriendlyException throwable) { - - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/queue/FairQueue.java b/src/main/java/dev/cosgy/TextToSpeak/queue/FairQueue.java deleted file mode 100644 index a34f3a4..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/queue/FairQueue.java +++ /dev/null @@ -1,89 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.queue; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class FairQueue { - private final List list = new ArrayList<>(); - private final Set set = new HashSet<>(); - - public int add(T item) { - int lastIndex; - for (lastIndex = list.size() - 1; lastIndex > -1; lastIndex--) - if (list.get(lastIndex).getIdentifier() == item.getIdentifier()) - break; - lastIndex++; - set.clear(); - for (; lastIndex < list.size(); lastIndex++) { - if (set.contains(list.get(lastIndex).getIdentifier())) - break; - set.add(list.get(lastIndex).getIdentifier()); - } - list.add(lastIndex, item); - return lastIndex; - } - - public void addAt(int index, T item) { - if (index >= list.size()) - list.add(item); - else - list.add(index, item); - } - - public int size() { - return list.size(); - } - - public T pull() { - return list.remove(0); - } - - public boolean isEmpty() { - return list.isEmpty(); - } - - public List getList() { - return list; - } - - public T get(int index) { - return list.get(index); - } - - public T remove(int index) { - return list.remove(index); - } - - public int removeAll(long identifier) { - int count = 0; - for (int i = list.size() - 1; i >= 0; i--) { - if (list.get(i).getIdentifier() == identifier) { - list.remove(i); - count++; - } - } - return count; - } - - public void clear() { - list.clear(); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/settings/Settings.java b/src/main/java/dev/cosgy/TextToSpeak/settings/Settings.java deleted file mode 100644 index 7db2d8c..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/settings/Settings.java +++ /dev/null @@ -1,107 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.settings; - -import com.jagrosh.jdautilities.command.GuildSettingsProvider; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; - -import java.util.Collection; -import java.util.Collections; - -public class Settings implements GuildSettingsProvider { - private final SettingsManager manager; - protected long textId; - private String prefix; - private int volume; - private boolean readName; - private boolean joinAndLeaveRead; - - - public Settings(SettingsManager manager, String textId, String prefix, int volume, boolean readName, boolean joinAndLeaveRead) { - this.manager = manager; - try { - this.textId = Long.parseLong(textId); - } catch (NumberFormatException e) { - this.textId = 0; - } - this.prefix = prefix; - this.volume = volume; - this.readName = readName; - this.joinAndLeaveRead = joinAndLeaveRead; - } - - public Settings(SettingsManager manager, long textId, String prefix, int volume, boolean readName, boolean joinAndLeaveRead) { - this.manager = manager; - this.textId = textId; - this.prefix = prefix; - this.volume = volume; - this.readName = readName; - this.joinAndLeaveRead = joinAndLeaveRead; - } - - - public TextChannel getTextChannel(Guild guild) { - return guild == null ? null : guild.getTextChannelById(textId); - } - - public void setTextChannel(TextChannel tc) { - this.textId = tc == null ? 0 : tc.getIdLong(); - this.manager.writeSettings(); - } - - public int getVolume() { - return volume; - } - - public void setVolume(int volume) { - this.volume = volume; - this.manager.writeSettings(); - } - - public String getPrefix() { - return prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - this.manager.writeSettings(); - } - - @Override - public Collection getPrefixes() { - return prefix == null ? Collections.EMPTY_SET : Collections.singleton(prefix); - } - - public boolean isReadName() { - return readName; - } - - public void setReadName(boolean readName) { - this.readName = readName; - this.manager.writeSettings(); - } - - public boolean isJoinAndLeaveRead() { - return joinAndLeaveRead; - } - - public void setJoinAndLeaveRead(boolean joinAndLeaveRead) { - this.joinAndLeaveRead = joinAndLeaveRead; - this.manager.writeSettings(); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/settings/SettingsManager.java b/src/main/java/dev/cosgy/TextToSpeak/settings/SettingsManager.java deleted file mode 100644 index 8b678ab..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/settings/SettingsManager.java +++ /dev/null @@ -1,96 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.settings; - -import com.jagrosh.jdautilities.command.GuildSettingsManager; -import dev.cosgy.TextToSpeak.utils.OtherUtil; -import net.dv8tion.jda.api.entities.Guild; -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Files; -import java.util.HashMap; - -public class SettingsManager implements GuildSettingsManager { - private final HashMap settings; - - public SettingsManager() { - this.settings = new HashMap<>(); - try { - JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(OtherUtil.getPath("serversettings.json")))); - loadedSettings.keySet().forEach((id) -> { - JSONObject o = loadedSettings.getJSONObject(id); - settings.put(Long.parseLong(id), new Settings(this, - o.has("text_channel_id") ? o.getString("text_channel_id") : null, - o.has("prefix") ? o.getString("prefix") : null, - o.has("volume") ? o.getInt("volume") : 50, - o.has("read_name") && o.getBoolean("read_name"), - o.has("join_and_leave_read") && o.getBoolean("join_and_leave_read") - )); - }); - } catch (IOException | JSONException e) { - LoggerFactory.getLogger("Settings").warn("サーバー設定を読み込めませんでした(まだ設定がない場合は正常です): " + e); - } - } - - @Override - public Settings getSettings(Guild guild) { - return getSettings(guild.getIdLong()); - } - - public Settings getSettings(long guildId) { - return settings.computeIfAbsent(guildId, id -> createDefaultSettings()); - } - - /** - * デフォルト設定のデータを作って返す。 - * - * @return 作成されたデフォルト設定 - */ - private Settings createDefaultSettings() { - return new Settings(this, 0, null, 50, false, false); - } - - /** - * 設定をファイルに書き込む - */ - protected void writeSettings() { - JSONObject obj = new JSONObject(); - settings.keySet().forEach(key -> { - JSONObject o = new JSONObject(); - Settings s = settings.get(key); - if (s.textId != 0) - o.put("text_channel_id", Long.toString(s.textId)); - if (s.getPrefix() != null) - o.put("prefix", s.getPrefix()); - if (s.getVolume() != 50) - o.put("volume", s.getVolume()); - if (s.isReadName()) - o.put("read_name", s.isReadName()); - if (s.isJoinAndLeaveRead()) - o.put("join_and_leave_read", s.isJoinAndLeaveRead()); - obj.put(Long.toString(key), o); - }); - try { - Files.write(OtherUtil.getPath("serversettings.json"), obj.toString(4).getBytes()); - } catch (IOException ex) { - LoggerFactory.getLogger("Settings").warn("ファイルへの書き込みに失敗しました: " + ex); - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/settings/UserSettings.java b/src/main/java/dev/cosgy/TextToSpeak/settings/UserSettings.java deleted file mode 100644 index 627ac61..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/settings/UserSettings.java +++ /dev/null @@ -1,89 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.settings; - -public class UserSettings { - private final UserSettingsManager manager; - private final Long userId; - private String voice; - private float speed; - private float intonation; - private float voiceQualityA; - private float voiceQualityFm; - - public UserSettings(UserSettingsManager manager, Long userId, String voice, float speed, float intonation, float voiceQualityA, float voiceQualityFm) { - this.manager = manager; - this.userId = userId; - this.voice = voice; - this.speed = speed; - this.intonation = intonation; - this.voiceQualityA = voiceQualityA; - this.voiceQualityFm = voiceQualityFm; - } - - - // getter - public Long getUserId() { - return userId; - } - - public String getVoice() { - return voice; - } - - // setter - public void setVoice(String voice) { - this.voice = voice; - this.manager.saveSetting(userId); - } - - public float getSpeed() { - return speed; - } - - public void setSpeed(float speed) { - this.speed = speed; - this.manager.saveSetting(userId); - } - - public float getIntonation() { - return intonation; - } - - public void setIntonation(float intonation) { - this.intonation = intonation; - this.manager.saveSetting(userId); - } - - public float getVoiceQualityA() { - return voiceQualityA; - } - - public void setVoiceQualityA(float voiceQualityA) { - this.voiceQualityA = voiceQualityA; - this.manager.saveSetting(userId); - } - - public float getVoiceQualityFm() { - return voiceQualityFm; - } - - public void setVoiceQualityFm(float voiceQualityFm) { - this.voiceQualityFm = voiceQualityFm; - this.manager.saveSetting(userId); - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/settings/UserSettingsManager.java b/src/main/java/dev/cosgy/TextToSpeak/settings/UserSettingsManager.java deleted file mode 100644 index eca6c91..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/settings/UserSettingsManager.java +++ /dev/null @@ -1,99 +0,0 @@ -package dev.cosgy.TextToSpeak.settings; - -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -import dev.cosgy.TextToSpeak.utils.OtherUtil; -import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.sql.*; -import java.util.HashMap; - -public class UserSettingsManager { - private final HashMap settings; - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private Connection connection; - - public UserSettingsManager() { - this.settings = new HashMap<>(); - Path path = OtherUtil.getPath("UserData.sqlite"); - boolean create = false; - if (!path.toFile().exists()) { - create = true; - String original = OtherUtil.loadResource(this, "UserData.sqlite"); - try { - FileUtils.writeStringToFile(path.toFile(), original, StandardCharsets.UTF_8); - logger.info("データベースファイルが存在しなかったためファイルを作成しました。"); - } catch (IOException e) { - logger.error("データベースファイルを作成できませんでした。", e); - } - } - - try { - connection = DriverManager.getConnection("jdbc:sqlite:UserData.sqlite"); - Statement statement = connection.createStatement(); - String sql = "create table if not exists settings ( id integer not null constraint settings_pk primary key, voice TEXT, speed real, intonation real, voiceQualityA real, voiceQualityFm real)"; - statement.execute(sql); - - ResultSet rs = statement.executeQuery("select * from settings"); - while (rs.next()) { - settings.put(rs.getLong(1), new UserSettings(this, rs.getLong(1), rs.getString(2), rs.getFloat(3), rs.getFloat(4), rs.getFloat(5), rs.getFloat(6))); - } - } catch (SQLException throwables) { - logger.error("データベースに接続できませんでした。", throwables); - } - } - - public UserSettings getSettings(long userId) { - return settings.computeIfAbsent(userId, this::createDefaultSettings); - } - - private UserSettings createDefaultSettings(Long userId) { - return new UserSettings(this, userId, "mei_normal", 1.0f, 1.0f, 0.5f, 2.0f); - } - - protected void saveSetting(Long userId) { - String sql = "REPLACE INTO settings (id, voice, speed, intonation, voiceQualityA, voiceQualityFm) VALUES (?,?,?,?,?,?)"; - UserSettings settings = this.settings.get(userId); - try (PreparedStatement ps = connection.prepareStatement(sql)) { - ps.setLong(1, userId); - ps.setString(2, settings.getVoice()); - ps.setFloat(3, settings.getSpeed()); - ps.setFloat(4, settings.getIntonation()); - ps.setFloat(5, settings.getVoiceQualityA()); - ps.setFloat(6, settings.getVoiceQualityFm()); - - logger.debug(ps.toString()); - ps.executeUpdate(); - } catch (SQLException throwables) { - logger.error("設定を保存できませんでした。", throwables); - } - } - - public void closeConnection() { - try { - connection.close(); - logger.info("データベース接続を終了しました。"); - } catch (SQLException e) { - logger.error("データベース接続を終了できませんでした。", e); - } - } -} diff --git a/src/main/java/dev/cosgy/TextToSpeak/utils/OtherUtil.java b/src/main/java/dev/cosgy/TextToSpeak/utils/OtherUtil.java deleted file mode 100644 index 11b3c72..0000000 --- a/src/main/java/dev/cosgy/TextToSpeak/utils/OtherUtil.java +++ /dev/null @@ -1,180 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// Copyright 2023 Cosgy Dev / -// / -// Licensed under the Apache License, Version 2.0 (the "License"); / -// you may not use this file except in compliance with the License. / -// You may obtain a copy of the License at / -// / -// http://www.apache.org/licenses/LICENSE-2.0 / -// / -// Unless required by applicable law or agreed to in writing, software / -// distributed under the License is distributed on an "AS IS" BASIS, / -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / -// See the License for the specific language governing permissions and / -// limitations under the License. / -////////////////////////////////////////////////////////////////////////////////////////// - -package dev.cosgy.TextToSpeak.utils; - -import dev.cosgy.TextToSpeak.TextToSpeak; -import dev.cosgy.TextToSpeak.entities.Prompt; -import net.dv8tion.jda.api.OnlineStatus; -import net.dv8tion.jda.api.entities.Activity; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.json.JSONException; -import org.json.JSONObject; -import org.json.JSONTokener; - -import java.io.*; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class OtherUtil { - public final static String NEW_VERSION_AVAILABLE = "利用可能な新しいバージョンがあります!\n" - + "現在のバージョン: %s\n" - + "最新のバージョン: %s\n\n" - + " https://github.com/Cosgy-Dev/TextToSpeakBot/releases/latest から最新バージョンをダウンロードして下さい。"; - private final static String WINDOWS_INVALID_PATH = "c:\\windows\\system32\\"; - - public static Path getPath(String path) { - // special logic to prevent trying to access system32 - if (path.toLowerCase().startsWith(WINDOWS_INVALID_PATH)) { - String filename = path.substring(WINDOWS_INVALID_PATH.length()); - try { - path = new File(TextToSpeak.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getPath() + File.separator + filename; - } catch (URISyntaxException ignored) { - } - } - return Paths.get(path); - } - - /** - * jarからリソースを文字列としてロードします - * - * @param clazz クラスベースオブジェクト - * @param name リソースの名前 - * @return リソースの内容を含む文字列 - */ - public static String loadResource(Object clazz, String name) { - try { - return readString(clazz.getClass().getResourceAsStream(name)); - } catch (Exception ex) { - return null; - } - } - - public static String readString(InputStream inputStream) throws IOException { - ByteArrayOutputStream into = new ByteArrayOutputStream(); - byte[] buf = new byte[32768]; - for (int n; 0 < (n = inputStream.read(buf)); ) { - into.write(buf, 0, n); - } - into.close(); - return into.toString("utf-8"); - } - - /** - * URLから画像データをロード - * - * @param url 画像のURL - * @return URLのinputstream - */ - public static InputStream imageFromUrl(String url) { - if (url == null) - return null; - try { - URL u = new URL(url); - URLConnection urlConnection = u.openConnection(); - urlConnection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36"); - return urlConnection.getInputStream(); - } catch (IOException | IllegalArgumentException ignore) { - } - return null; - } - - /** - * 文字列からアクティビティを解析 - * - * @param game the game, including the action such as 'playing' or 'watching' - * @return the parsed activity - */ - public static Activity parseGame(String game) { - if (game == null || game.trim().isEmpty() || game.trim().equalsIgnoreCase("default")) - return null; - String lower = game.toLowerCase(); - if (lower.startsWith("playing")) - return Activity.playing(makeNonEmpty(game.substring(7).trim())); - if (lower.startsWith("listening to")) - return Activity.listening(makeNonEmpty(game.substring(12).trim())); - if (lower.startsWith("listening")) - return Activity.listening(makeNonEmpty(game.substring(9).trim())); - if (lower.startsWith("watching")) - return Activity.watching(makeNonEmpty(game.substring(8).trim())); - if (lower.startsWith("streaming")) { - String[] parts = game.substring(9).trim().split("\\s+", 2); - if (parts.length == 2) { - return Activity.streaming(makeNonEmpty(parts[1]), "https://twitch.tv/" + parts[0]); - } - } - return Activity.playing(game); - } - - public static String makeNonEmpty(String str) { - return str == null || str.isEmpty() ? "\u200B" : str; - } - - public static OnlineStatus parseStatus(String status) { - if (status == null || status.trim().isEmpty()) - return OnlineStatus.ONLINE; - OnlineStatus st = OnlineStatus.fromKey(status); - return st == null ? OnlineStatus.ONLINE : st; - } - - public static String checkVersion(Prompt prompt) { - // Get current version number - String version = getCurrentVersion(); - - // Check for new version - String latestVersion = getLatestVersion(); - - if (latestVersion != null && !latestVersion.equals(version) && TextToSpeak.CHECK_UPDATE) { - prompt.alert(Prompt.Level.WARNING, "Version", String.format(NEW_VERSION_AVAILABLE, version, latestVersion)); - } - - // Return the current version - return version; - } - - public static String getCurrentVersion() { - if (TextToSpeak.class.getPackage() != null && TextToSpeak.class.getPackage().getImplementationVersion() != null) - return TextToSpeak.class.getPackage().getImplementationVersion(); - else - return "不明"; - } - - public static String getLatestVersion() { - try { - Response response = new OkHttpClient.Builder().build() - .newCall(new Request.Builder().get().url("https://api.github.com/repos/Cosgy-Dev/TextToSpeakBot/releases/latest").build()) - .execute(); - ResponseBody body = response.body(); - if (body != null) { - try (Reader reader = body.charStream()) { - JSONObject obj = new JSONObject(new JSONTokener(reader)); - return obj.getString("tag_name"); - } finally { - response.close(); - } - } else - return null; - } catch (IOException | JSONException | NullPointerException ex) { - return null; - } - } -} diff --git a/src/main/java/dev/cosgy/textToSpeak/Bot.kt b/src/main/java/dev/cosgy/textToSpeak/Bot.kt new file mode 100644 index 0000000..a76ddca --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/Bot.kt @@ -0,0 +1,144 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak + +import com.jagrosh.jdautilities.commons.waiter.EventWaiter +import dev.cosgy.textToSpeak.audio.* +import dev.cosgy.textToSpeak.audio.Dictionary +import dev.cosgy.textToSpeak.gui.GUI +import dev.cosgy.textToSpeak.settings.SettingsManager +import dev.cosgy.textToSpeak.settings.UserSettingsManager +import net.dv8tion.jda.api.JDA +import net.dv8tion.jda.api.entities.Guild +import org.apache.commons.io.FileUtils +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File +import java.io.IOException +import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit +import java.util.function.Consumer +import kotlin.system.exitProcess + +class Bot(val waiter: EventWaiter, val config: BotConfig, val settingsManager: SettingsManager) { + val threadpool: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() + val playerManager: PlayerManager = PlayerManager(this) + val voiceCreation: VoiceCreation + val userSettingsManager: UserSettingsManager + private val aloneInVoiceHandler: AloneInVoiceHandler + var log: Logger = LoggerFactory.getLogger(this.javaClass) + var dictionary: Dictionary? = null + private set + private var shuttingDown = false + var jda: JDA? = null + private var gui: GUI? = null + + init { + playerManager.init() + voiceCreation = VoiceCreation(this) + userSettingsManager = UserSettingsManager() + aloneInVoiceHandler = AloneInVoiceHandler(this) + aloneInVoiceHandler.init() + } + + fun readyJDA() { + dictionary = Dictionary.getInstance(this) + } + + fun closeAudioConnection(guildId: Long) { + val guild = jda!!.getGuildById(guildId) + if (guild != null) threadpool.submit { guild.audioManager.closeAudioConnection() } + } + + fun resetGame() { + val game = if (config.game == null || config.game!!.name.lowercase(Locale.getDefault()) + .matches("(none|なし)".toRegex()) + ) null else config.game + if (jda!!.presence.activity != game) jda!!.presence.activity = game + } + + fun shutdown() { + if (shuttingDown) return + shuttingDown = true + if (jda!!.status != JDA.Status.SHUTTING_DOWN) { + jda!!.guilds.forEach(Consumer { g: Guild -> + val am = g.audioManager + if (am.isConnected) { + am.closeAudioConnection() + val ah = am.sendingHandler as AudioHandler? + if (ah != null) { + ah.stopAndClear() + ah.player.destroy() + } + } + }) + + userSettingsManager.closeConnection() + // Wait for any remaining tasks to complete before shutting down the thread pool + threadpool.shutdown() + try { + if (!threadpool.awaitTermination(10, TimeUnit.SECONDS)) { + threadpool.shutdownNow() + if (!threadpool.awaitTermination(10, TimeUnit.SECONDS)) { + log.warn("Thread pool did not terminate") + } + } + } catch (e: InterruptedException) { + threadpool.shutdownNow() + Thread.currentThread().interrupt() + log.warn("Thread pool shutdown was interrupted") + } + } + + // Shutdown JDA + jda?.shutdown() + try { + if (!jda!!.awaitShutdown(10, TimeUnit.SECONDS)) { + log.warn("JDA did not shutdown properly") + } + } catch (e: InterruptedException) { + jda!!.shutdownNow() + Thread.currentThread().interrupt() + log.warn("JDA shutdown was interrupted") + } + + dictionary?.close() + // Delete temporary files + try { + FileUtils.cleanDirectory(File("tmp")) + FileUtils.cleanDirectory(File("wav")) + log.info("Deleted temporary files") + } catch (e: IOException) { + log.warn("Failed to delete temporary files") + } catch (e: IllegalArgumentException) { + log.warn("One or more directory paths were invalid.") + } + + if (gui != null) gui!!.dispose() + exitProcess(0) + } + + + fun setGUI(gui: GUI?) { + this.gui = gui + } + + companion object { + var INSTANCE: Bot? = null + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/BotConfig.kt b/src/main/java/dev/cosgy/textToSpeak/BotConfig.kt new file mode 100644 index 0000000..a6079fa --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/BotConfig.kt @@ -0,0 +1,194 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak + +import com.typesafe.config.ConfigException +import com.typesafe.config.ConfigFactory +import dev.cosgy.textToSpeak.entities.Prompt +import dev.cosgy.textToSpeak.utils.OtherUtil +import net.dv8tion.jda.api.OnlineStatus +import net.dv8tion.jda.api.entities.Activity +import org.apache.commons.io.FileUtils +import java.io.IOException +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import kotlin.system.exitProcess + +class BotConfig(private val prompt: Prompt) { + private var path: Path? = null + var token: String? = null + private set + var prefix: String? = null + private set + private var altprefix: String? = null + var dictionary: String? = null + private set + var voiceDirectory: String? = null + private set + var winJTalkDir: String? = null + private set + var ownerId: Long = 0 + private set + var aloneTimeUntilStop: Long = 0 + private set + var maxMessageCount = 0 + private set + var status: OnlineStatus? = null + private set + var game: Activity? = null + private set + private var updatealerts = false + private var dBots = false + var helpToDm = false + private set + var isValid = false + private set + + var isForceUTF8 = false + private set + + fun load() { + isValid = false + try { + path = OtherUtil.getPath(System.getProperty("config.file", System.getProperty("config", "config.txt"))) + if (path!!.toFile().exists()) { + if (System.getProperty("config.file") == null) System.setProperty( + "config.file", + System.getProperty("config", "config.txt") + ) + ConfigFactory.invalidateCaches() + } + + // load in the config file, plus the default values + //Config = ConfigFactory.parseFile(path.toFile()).withFallback(ConfigFactory.load()); + val config = ConfigFactory.load() + token = config.getString("token") + prefix = config.getString("prefix") + altprefix = config.getString("altprefix") + ownerId = if (config.getAnyRef("owner") is String) 0L else config.getLong("owner") + game = OtherUtil.parseGame(config.getString("game")) + status = OtherUtil.parseStatus(config.getString("status")) + updatealerts = config.getBoolean("updatealerts") + dictionary = config.getString("dictionary") + voiceDirectory = config.getString("voiceDirectory") + aloneTimeUntilStop = config.getLong("alonetimeuntilstop") + maxMessageCount = config.getInt("maxmessagecount") + winJTalkDir = config.getString("winjtalkdir") + helpToDm = config.getBoolean("helptodm") + isForceUTF8 = config.getBoolean("forceutf-8") + dBots = ownerId == 334091398263341056 + var write = false + + // validate bot token + if (token == null || token!!.isEmpty() || token!!.matches("(BOT_TOKEN_HERE|Botトークンをここに貼り付け)".toRegex())) { + token = prompt.prompt( + """ + BOTトークンを入力してください。 + BOTトークン: + """.trimIndent() + ) + write = if (token == null) { + prompt.alert( + Prompt.Level.WARNING, CONTEXT, """ + トークンが入力されていません!終了します。 + + 設定ファイルの場所: ${path!!.toAbsolutePath()} + """.trimIndent() + ) + return + } else { + true + } + } + + // validate bot owner + if (ownerId <= 0) { + ownerId = try { + prompt.prompt( + """ + 所有者のユーザーIDが設定されていない、または有効なIDではありません。 + BOTの所有者のユーザーIDを入力してください。 + 所有者のユーザーID: + """.trimIndent() + )!!.toLong() + } catch (ex: NumberFormatException) { + 0 + } catch (ex: NullPointerException) { + 0 + } + if (ownerId <= 0) { + prompt.alert( + Prompt.Level.ERROR, CONTEXT, + """ + 無効なユーザーIDです!終了します。 + 設定ファイルの場所: ${path!!.toAbsolutePath()} + """.trimIndent() + ) + exitProcess(0) + } else { + write = true + } + } + if (write) { + val original = OtherUtil.loadResource(this, "/reference.conf") + val mod: String = + original?.substring(original.indexOf(START_TOKEN) + START_TOKEN.length, original.indexOf(END_TOKEN)) + ?.replace("BOT_TOKEN_HERE", token!!)?.replace("Botトークンをここに貼り付け", token!!) + ?.replace("0 // OWNER ID", ownerId.toString()) + ?.replace("所有者IDをここに貼り付け", ownerId.toString())?.trim { it <= ' ' } + ?: """ + token = $token + owner = $ownerId + """.trimIndent() + FileUtils.writeStringToFile(path!!.toFile(), mod, StandardCharsets.UTF_8) + } + + // if we get through the whole config, it's good to go + isValid = true + } catch (ex: ConfigException) { + prompt.alert( + Prompt.Level.ERROR, CONTEXT, """ + $ex: ${ex.message} + + 設定ファイルの場所: ${path!!.toAbsolutePath()} + """.trimIndent() + ) + } catch (ex: IOException) { + prompt.alert( + Prompt.Level.ERROR, CONTEXT, """ + $ex: ${ex.message} + + 設定ファイルの場所: ${path!!.toAbsolutePath()} + """.trimIndent() + ) + } + } + + val configLocation: String + get() = path!!.toFile().absolutePath + val altPrefix: String? + get() = if ("NONE".equals(altprefix, ignoreCase = true)) null else altprefix + + fun useUpdateAlerts(): Boolean { + return updatealerts + } + + companion object { + private const val CONTEXT = "Config" + private const val START_TOKEN = "/// START OF YOMIAGEBOT CONFIG ///" + private const val END_TOKEN = "/// END OF YOMIAGEBOT CONFIG ///" + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/Listener.kt b/src/main/java/dev/cosgy/textToSpeak/Listener.kt new file mode 100644 index 0000000..54b2259 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/Listener.kt @@ -0,0 +1,127 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak + +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist +import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import dev.cosgy.textToSpeak.audio.AudioHandler +import dev.cosgy.textToSpeak.audio.QueuedTrack +import dev.cosgy.textToSpeak.utils.OtherUtil +import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel +import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent +import net.dv8tion.jda.api.events.session.ReadyEvent +import net.dv8tion.jda.api.events.session.ShutdownEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.IOException +import java.util.* +import java.util.concurrent.TimeUnit + +class Listener(private val bot: Bot) : ListenerAdapter() { + var log: Logger = LoggerFactory.getLogger(this.javaClass) + override fun onReady(event: ReadyEvent) { + if (event.jda.guilds.isEmpty()) { + val log = LoggerFactory.getLogger("TTSBot") + log.warn("このボットはグループに入っていません!ボットをあなたのグループに追加するには、以下のリンクを使用してください。") + log.warn(event.jda.getInviteUrl(*TextToSpeak.RECOMMENDED_PERMS)) + } + if (bot.config.useUpdateAlerts()) { + bot.threadpool.scheduleWithFixedDelay({ + val owner = bot.jda?.getUserById(bot.config.ownerId) + if (owner != null) { + val currentVersion = OtherUtil.currentVersion + val latestVersion = OtherUtil.latestVersion + if (latestVersion != null && !currentVersion.equals( + latestVersion, + ignoreCase = true + ) && TextToSpeak.CHECK_UPDATE + ) { + val msg = String.format(OtherUtil.NEW_VERSION_AVAILABLE, currentVersion, latestVersion) + owner.openPrivateChannel().queue { pc: PrivateChannel -> pc.sendMessage(msg).queue() } + } + } + }, 0, 24, TimeUnit.HOURS) + } + } + + override fun onGuildVoiceUpdate(event: GuildVoiceUpdateEvent) { + val botMember = event.guild.selfMember + val settings = bot.settingsManager.getSettings(event.guild) + if (event.channelLeft != null) { + if (settings.isJoinAndLeaveRead() && Objects.requireNonNull(event.guild.selfMember.voiceState)?.channel === event.channelLeft && event.channelLeft!!.members.size > 1) { + val file: String? = try { + bot.voiceCreation.createVoice( + event.guild, + event.member.user, + event.member.user.name + "がボイスチャンネルから退出しました。" + ) + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + bot.playerManager.loadItemOrdered(event.guild, file, ResultHandler(event)) + } + if (event.channelLeft!!.members.size == 1 && event.channelLeft!!.members.contains(botMember)) { + val handler = event.guild.audioManager.sendingHandler as AudioHandler? + handler!!.queue.clear() + try { + bot.voiceCreation.clearGuildFolder(event.guild) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + } + if (event.channelJoined != null) { + if (settings.isJoinAndLeaveRead() && Objects.requireNonNull(event.guild.selfMember.voiceState)?.channel === event.channelJoined) { + val file: String? = try { + bot.voiceCreation.createVoice( + event.guild, + event.member.user, + event.member.user.name + "がボイスチャンネルに参加しました。" + ) + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + bot.playerManager.loadItemOrdered(event.guild, file, ResultHandler(event)) + } + } + } + + override fun onShutdown(event: ShutdownEvent) { + bot.shutdown() + } + + private inner class ResultHandler(private val event: GuildVoiceUpdateEvent) : AudioLoadResultHandler { + private fun loadSingle(track: AudioTrack) { + val handler = event.guild.audioManager.sendingHandler as AudioHandler? + handler!!.addTrack(QueuedTrack(track, event.member.user)) + } + + override fun trackLoaded(track: AudioTrack) { + loadSingle(track) + } + + override fun playlistLoaded(playlist: AudioPlaylist) {} + override fun noMatches() {} + override fun loadFailed(throwable: FriendlyException) {} + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/TextToSpeak.kt b/src/main/java/dev/cosgy/textToSpeak/TextToSpeak.kt new file mode 100644 index 0000000..4579036 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/TextToSpeak.kt @@ -0,0 +1,230 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak + +import com.github.lalyos.jfiglet.FigletFont +import com.jagrosh.jdautilities.command.CommandClientBuilder +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.commons.waiter.EventWaiter +import dev.cosgy.textToSpeak.commands.admin.GuildSettings +import dev.cosgy.textToSpeak.commands.admin.JLReadCmd +import dev.cosgy.textToSpeak.commands.admin.SetReadNameCmd +import dev.cosgy.textToSpeak.commands.admin.SettcCmd +import dev.cosgy.textToSpeak.commands.dictionary.AddWordCmd +import dev.cosgy.textToSpeak.commands.dictionary.DlWordCmd +import dev.cosgy.textToSpeak.commands.dictionary.WordListCmd +import dev.cosgy.textToSpeak.commands.general.* +import dev.cosgy.textToSpeak.commands.owner.ShutdownCmd +import dev.cosgy.textToSpeak.entities.Prompt +import dev.cosgy.textToSpeak.gui.GUI +import dev.cosgy.textToSpeak.listeners.CommandAudit +import dev.cosgy.textToSpeak.listeners.MessageListener +import dev.cosgy.textToSpeak.settings.SettingsManager +import dev.cosgy.textToSpeak.utils.OtherUtil +import net.dv8tion.jda.api.JDABuilder +import net.dv8tion.jda.api.OnlineStatus +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.Activity +import net.dv8tion.jda.api.exceptions.InvalidTokenException +import net.dv8tion.jda.api.requests.GatewayIntent +import net.dv8tion.jda.api.utils.cache.CacheFlag +import org.slf4j.LoggerFactory +import sun.misc.Signal +import java.awt.Color +import java.io.IOException +import java.util.* +import kotlin.system.exitProcess + +object TextToSpeak { + val RECOMMENDED_PERMS = arrayOf( + Permission.VIEW_CHANNEL, + Permission.MESSAGE_SEND, + Permission.MESSAGE_HISTORY, + Permission.MESSAGE_ADD_REACTION, + Permission.MESSAGE_EMBED_LINKS, + Permission.MESSAGE_ATTACH_FILES, + Permission.MESSAGE_MANAGE, + Permission.MESSAGE_EXT_EMOJI, + Permission.USE_APPLICATION_COMMANDS, + Permission.MANAGE_CHANNEL, + Permission.VOICE_CONNECT, + Permission.VOICE_SPEAK, + Permission.NICKNAME_CHANGE + ) + private val INTENTS = arrayOf( + GatewayIntent.DIRECT_MESSAGES, + GatewayIntent.GUILD_MESSAGES, + GatewayIntent.GUILD_MESSAGE_REACTIONS, + GatewayIntent.GUILD_VOICE_STATES, + GatewayIntent.MESSAGE_CONTENT + ) + var CHECK_UPDATE = true + var COMMAND_AUDIT_ENABLED = false + + /** + * @param args コマンドライン引数 + */ + @JvmStatic + fun main(args: Array) { + val log = LoggerFactory.getLogger("Startup") + try { + println( + """ + ${FigletFont.convertOneLine("TextToSpeak Bot v" + OtherUtil.currentVersion)} + by Cosgy Dev + """.trimIndent() + ) + } catch (ignored: IOException) { + } + val prompt = Prompt( + "TextToSpeak Bot", "noguiモードに切り替えます。 -Dnogui=trueフラグを含めると、手動でnoguiモードで起動できます。", + "true".equals(System.getProperty("nogui", "false"), ignoreCase = true) + ) + + // check deprecated nogui mode (new way of setting it is -Dnogui=true) + for (arg in args) if ("-nogui".equals(arg, ignoreCase = true)) { + prompt.alert( + Prompt.Level.WARNING, "GUI", "-noguiフラグは廃止予定です。 " + + "jarの名前の前に-Dnogui = trueフラグを使用してください。 例:java -jar -Dnogui=true JMusicBot.jar" + ) + } else if ("-nocheckupdates".equals(arg, ignoreCase = true)) { + CHECK_UPDATE = false + log.info("アップデートチェックを無効にしました") + } else if ("-auditcommands".equals(arg, ignoreCase = true)) { + COMMAND_AUDIT_ENABLED = true + log.info("実行されたコマンドの記録を有効にしました。") + } + val version = OtherUtil.checkVersion(prompt) + if (!System.getProperty("java.vm.name").contains("64")) prompt.alert( + Prompt.Level.WARNING, + "Java Version", + "サポートされていないJavaバージョンを使用しています。64ビット版のJavaを使用してください。" + ) + val config = BotConfig(prompt) + config.load() + if (!config.isValid) return + val waiter = EventWaiter() + val settings = SettingsManager() + val bot = Bot(waiter, config, settings) + Bot.INSTANCE = bot + val aboutCommand = AboutCommand( + Color.BLUE.brighter(), + "TextToSpeak Bot by Cosgy Dev(V$version)", + *RECOMMENDED_PERMS + ) + aboutCommand.setIsAuthor(false) + aboutCommand.setReplacementCharacter("🎶") + val cb = CommandClientBuilder() + .setPrefix(config.prefix) + .setAlternativePrefix(config.altPrefix) + .setOwnerId(config.ownerId.toString()) //.setEmojis(config.getSuccess(), config.getWarning(), config.getError()) + .setHelpWord("help") + .setLinkedCacheSize(200) + .setGuildSettingsManager(settings) + .setListener(CommandAudit()) + val slashCommandList: ArrayList = object : ArrayList() { + init { + add(aboutCommand) + add(HelpCmd(bot)) + add(JoinCmd(bot)) + add(ByeCmd(bot)) + add(SettingsCmd(bot)) + add(SetVoiceCmd(bot)) + add(SetSpeedCmd(bot)) + add(SetIntonationCmd(bot)) + add(SetVoiceQualityA(bot)) + add(SetVoiceQualityFm(bot)) + add(AddWordCmd(bot)) + add(WordListCmd(bot)) + add(DlWordCmd(bot)) + add(SettcCmd(bot)) + add(SetReadNameCmd(bot)) + add(JLReadCmd(bot)) + add(GuildSettings(bot)) + add(ShutdownCmd(bot)) + } + } + cb.addSlashCommands(*slashCommandList.toTypedArray()) + cb.addCommands(*slashCommandList.toTypedArray()) + var nogame = false + if (config.status != OnlineStatus.UNKNOWN) cb.setStatus(config.status) + if (config.game == null) cb.setActivity(Activity.playing("/helpでヘルプを確認")) else if (config.game!!.name.lowercase( + Locale.getDefault() + ).matches("(none|なし)".toRegex()) + ) { + cb.setActivity(null) + nogame = true + } else cb.setActivity(config.game) + if (!prompt.isNoGUI) { + try { + val gui = GUI(bot) + bot.setGUI(gui) + gui.init() + } catch (e: Exception) { + log.error( + """ + GUIを開くことができませんでした。次の要因が考えられます: + サーバー上で実行している + 画面がない環境下で実行している + このエラーを非表示にするには、 -Dnogui=true フラグを使用してGUIなしモードで実行してください。 + """.trimIndent() + ) + } + } + log.info(config.configLocation + " から設定を読み込みました") + try { + val jda = JDABuilder.create(config.token, listOf(*INTENTS)) + .enableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE) + .disableCache( + CacheFlag.ACTIVITY, + CacheFlag.CLIENT_STATUS, + CacheFlag.EMOJI, + CacheFlag.ONLINE_STATUS, + CacheFlag.STICKER + ) + .setActivity(if (nogame) null else Activity.playing("準備中...")) + .setStatus(if (config.status == OnlineStatus.INVISIBLE || config.status == OnlineStatus.OFFLINE) OnlineStatus.INVISIBLE else OnlineStatus.DO_NOT_DISTURB) + .addEventListeners(cb.build(), waiter, Listener(bot), MessageListener(bot)) + .setBulkDeleteSplittingEnabled(true) + .build() + bot.jda = jda + } catch (ex: InvalidTokenException) { + prompt.alert( + Prompt.Level.ERROR, "TextToSpeak Bot", + """ + Botトークンでのログインに失敗しました。 + 正しいBotトークンが設定されていることを確認してください。(CLIENT SECRET ではありません!) + 設定ファイルの場所;${config.configLocation} + """.trimIndent() + ) + exitProcess(1) + } catch (ex: IllegalArgumentException) { + prompt.alert( + Prompt.Level.ERROR, "TextToSpeak Bot", + """ + 設定の一部が無効です:$ex + 設定ファイルの場所: ${config.configLocation} + """.trimIndent() + ) + exitProcess(1) + } + + Signal.handle(Signal("INT")) { _ -> + println("プログラムを終了しています...") + bot.shutdown() + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/audio/AloneInVoiceHandler.kt b/src/main/java/dev/cosgy/textToSpeak/audio/AloneInVoiceHandler.kt new file mode 100644 index 0000000..201ad2f --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/audio/AloneInVoiceHandler.kt @@ -0,0 +1,67 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.audio + +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.entities.Guild +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent +import java.time.Instant +import java.util.concurrent.TimeUnit +import java.util.function.Consumer + +class AloneInVoiceHandler(private val bot: Bot) { + private val aloneSince = HashMap() + private var aloneTimeUntilStop: Long = 0 + fun init() { + aloneTimeUntilStop = bot.config.aloneTimeUntilStop + if (aloneTimeUntilStop > 0) bot.threadpool.scheduleWithFixedDelay({ this.check() }, 0, 5, TimeUnit.SECONDS) + } + + private fun check() { + val toRemove: MutableSet = HashSet() + for ((key, value) in aloneSince) { + if (value.epochSecond > Instant.now().epochSecond - aloneTimeUntilStop) continue + val guild = bot.jda?.getGuildById(key) + if (guild == null) { + toRemove.add(key) + continue + } + (guild.audioManager.sendingHandler as AudioHandler?)!!.stopAndClear() + guild.audioManager.closeAudioConnection() + toRemove.add(key) + } + toRemove.forEach(Consumer { key: Long -> aloneSince.remove(key) }) + } + + fun onVoiceUpdate(event: GuildVoiceUpdateEvent) { + if (aloneTimeUntilStop <= 0) return + val guild = event.entity.guild + if (!bot.playerManager.hasHandler(guild)) return + val alone = isAlone(guild) + val inList = aloneSince.containsKey(guild.idLong) + if (!alone && inList) aloneSince.remove(guild.idLong) else if (alone && !inList) aloneSince[guild.idLong] = + Instant.now() + } + + private fun isAlone(guild: Guild): Boolean { + return if (guild.audioManager.connectedChannel == null) false else guild.audioManager.connectedChannel!!.members.stream() + .noneMatch { x: Member -> + (!x.voiceState!!.isDeafened + && !x.user.isBot) + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/audio/AudioHandler.kt b/src/main/java/dev/cosgy/textToSpeak/audio/AudioHandler.kt new file mode 100644 index 0000000..a3d2256 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/audio/AudioHandler.kt @@ -0,0 +1,91 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.audio + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer +import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter +import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason +import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame +import dev.cosgy.textToSpeak.queue.FairQueue +import net.dv8tion.jda.api.JDA +import net.dv8tion.jda.api.audio.AudioSendHandler +import net.dv8tion.jda.api.entities.Guild +import java.nio.ByteBuffer + +class AudioHandler(guild: Guild, val player: AudioPlayer) : AudioEventAdapter(), AudioSendHandler { + val queue = FairQueue() + val votes: Set = HashSet() + private val guildId: Long + private val stringGuildId: String + private var lastFrame: AudioFrame? = null + + init { + guildId = guild.idLong + stringGuildId = guild.id + } + + /** + * 再生キューに追加 + */ + fun addTrack(qtrack: QueuedTrack): Int { + return if (player.playingTrack == null) { + player.playTrack(qtrack.track) + -1 + } else queue.add(qtrack) + } + + fun stopAndClear() { + queue.clear() + player.stopTrack() + } + + fun isVoiceListening(jda: JDA): Boolean { + return guild(jda)!!.selfMember.voiceState!!.inAudioChannel() && player.playingTrack != null + } + + val requester: Long + get() = if (player.playingTrack == null || player.playingTrack.getUserData(Long::class.java) == null) 0 else player.playingTrack.getUserData( + Long::class.java + ) + + // Audio Events + override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) { + if (!queue.isEmpty) { + val qt = queue.pull() + player.playTrack(qt!!.track) + } + } + + // Audio Send Handler methods + override fun canProvide(): Boolean { + lastFrame = player.provide() + return lastFrame != null + } + + override fun provide20MsAudio(): ByteBuffer? { + return ByteBuffer.wrap(lastFrame!!.data) + } + + override fun isOpus(): Boolean { + return true + } + + // Private methods + private fun guild(jda: JDA): Guild? { + return jda.getGuildById(guildId) + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/audio/Dictionary.kt b/src/main/java/dev/cosgy/textToSpeak/audio/Dictionary.kt new file mode 100644 index 0000000..08d63b6 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/audio/Dictionary.kt @@ -0,0 +1,170 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.audio + +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.utils.OtherUtil +import org.slf4j.LoggerFactory +import java.nio.file.Path +import java.sql.Connection +import java.sql.DriverManager +import java.sql.SQLException +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.function.BiFunction + +class Dictionary private constructor(bot: Bot) { + private val logger = LoggerFactory.getLogger(this.javaClass) + private val path: Path? + private val create: Boolean + private var connection: Connection + private val guildDic: ConcurrentHashMap> = ConcurrentHashMap() + + init { + path = OtherUtil.getPath("UserData.sqlite") + create = !path.toFile().exists() + try { + Class.forName("org.sqlite.JDBC") + connection = DriverManager.getConnection("jdbc:sqlite:UserData.sqlite") + val statement = connection.createStatement() + statement.executeUpdate("CREATE TABLE IF NOT EXISTS Dictionary(guild_id integer,word text,reading)") + val guilds = bot.jda!!.guilds + for (value in guilds) { + val guildId = value.idLong + val optionalHashMap = getWordsFromDatabase(guildId) + optionalHashMap.ifPresent { hashMap: HashMap -> guildDic[guildId] = hashMap } + } + } catch (e: SQLException) { + logger.error("An error occurred while initializing the dictionary: ", e) + throw IllegalStateException(e) + } catch (e: ClassNotFoundException) { + logger.error("An error occurred while initializing the dictionary: ", e) + throw IllegalStateException(e) + } + logger.info("Dictionary initialization completed.") + } + + /** + * データベースとHashMapの内容を更新または新規追加します。 + * + * @param guildId サーバーID + * @param word 単語 + * @param reading 読み方 + */ + @Synchronized + fun updateDictionary(guildId: Long, word: String?, reading: String?) { + guildDic.compute(guildId) { _: Long?, v: HashMap? -> + val words: HashMap = v ?: HashMap() + words[word] = reading + executeUpdate(guildId, word, reading) + words + } + } + + /** + * データベースに登録されている単語を削除します。 + * + * @param guildId サーバーID + * @param word 単語 + * @return 正常に削除できた場合は `true`、削除時に問題が発生した場合は`false`を返します。 + */ + @Synchronized + fun deleteDictionary(guildId: Long, word: String?): Boolean { + guildDic.compute(guildId, BiFunction { _: Long?, v: HashMap? -> + if (v == null || !v.containsKey(word)) { + return@BiFunction null + } + val words = HashMap(v) + words.remove(word) + executeDelete(guildId, word) + words + }) + return true + } + + /** + * サーバーの辞書データを取得します。 + * + * @param guildId サーバーID + * @return `HashMap`形式の変数を返します。 + */ + fun getWords(guildId: Long): HashMap { + return guildDic.getOrDefault(guildId, HashMap()) + } + + private fun getWordsFromDatabase(guildId: Long): Optional> { + val sql = "SELECT * FROM Dictionary WHERE guild_id = ?" + try { + connection.prepareStatement(sql).use { ps -> + ps.setLong(1, guildId) + val rs = ps.executeQuery() + val word = HashMap() + while (rs.next()) { + word[rs.getString(2)] = rs.getString(3) + } + return Optional.of(word) + } + } catch (throwables: SQLException) { + logger.error("An error occurred while retrieving data from the dictionary: ", throwables) + return Optional.empty() + } + } + + private fun executeUpdate(guildId: Long, word: String?, reading: String?) { + val sql = "INSERT OR REPLACE INTO Dictionary(guild_id, word, reading) VALUES (?,?,?)" + try { + connection.prepareStatement(sql).use { ps -> + ps.setLong(1, guildId) + ps.setString(2, word) + ps.setString(3, reading) + ps.executeUpdate() + } + } catch (throwables: SQLException) { + logger.error("An error occurred while updating the dictionary: ", throwables) + } + } + + private fun executeDelete(guildId: Long, word: String?) { + val sql = "DELETE FROM Dictionary WHERE guild_id = ? AND word = ?" + try { + connection.prepareStatement(sql).use { ps -> + ps.setLong(1, guildId) + ps.setString(2, word) + ps.executeUpdate() + } + } catch (throwables: SQLException) { + logger.error("An error occurred while deleting from the dictionary: ", throwables) + } + } + + fun close() { + try { + connection.close() + } catch (throwables: SQLException) { + logger.error("An error occurred while closing the database connection: ", throwables) + } + } + + companion object { + private var instance: Dictionary? = null + fun getInstance(bot: Bot): Dictionary? { + if (instance == null) { + instance = Dictionary(bot) + } + return instance + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/audio/PlayerManager.kt b/src/main/java/dev/cosgy/textToSpeak/audio/PlayerManager.kt new file mode 100644 index 0000000..3c8b468 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/audio/PlayerManager.kt @@ -0,0 +1,54 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.audio + +import com.sedmelluq.discord.lavaplayer.player.AudioConfiguration +import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager +import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.entities.Guild +import org.slf4j.LoggerFactory + +class PlayerManager(val bot: Bot) : DefaultAudioPlayerManager() { + private val logger = LoggerFactory.getLogger(this.javaClass) + fun init() { + AudioSourceManagers.registerLocalSource(this) + if (configuration.opusEncodingQuality != 10) { + logger.debug("OpusEncodingQuality は、" + configuration.opusEncodingQuality + "(< 10)" + ", 品質を10に設定します。") + configuration.opusEncodingQuality = 10 + } + if (configuration.resamplingQuality != AudioConfiguration.ResamplingQuality.HIGH) { + logger.debug("ResamplingQuality は " + configuration.resamplingQuality.name + "(HIGHではない), 品質をHIGHに設定します。") + configuration.resamplingQuality = AudioConfiguration.ResamplingQuality.HIGH + } + } + + fun hasHandler(guild: Guild): Boolean { + return guild.audioManager.sendingHandler != null + } + + fun setUpHandler(guild: Guild): AudioHandler? { + val handler: AudioHandler? + if (guild.audioManager.sendingHandler == null) { + val player = createPlayer() + player.volume = bot.settingsManager.getSettings(guild).volume + handler = AudioHandler(guild, player) + player.addListener(handler) + guild.audioManager.sendingHandler = handler + } else handler = guild.audioManager.sendingHandler as AudioHandler? + return handler + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/audio/QueuedTrack.java b/src/main/java/dev/cosgy/textToSpeak/audio/QueuedTrack.kt similarity index 67% rename from src/main/java/dev/cosgy/TextToSpeak/audio/QueuedTrack.java rename to src/main/java/dev/cosgy/textToSpeak/audio/QueuedTrack.kt index 66ac184..eb56b20 100644 --- a/src/main/java/dev/cosgy/TextToSpeak/audio/QueuedTrack.java +++ b/src/main/java/dev/cosgy/textToSpeak/audio/QueuedTrack.kt @@ -13,31 +13,20 @@ // See the License for the specific language governing permissions and / // limitations under the License. / ////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.audio -package dev.cosgy.TextToSpeak.audio; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import dev.cosgy.textToSpeak.queue.Queueable +import net.dv8tion.jda.api.entities.User -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import dev.cosgy.TextToSpeak.queue.Queueable; -import net.dv8tion.jda.api.entities.User; +class QueuedTrack(val track: AudioTrack, owner: Long) : Queueable { -public class QueuedTrack implements Queueable { - private final AudioTrack track; + constructor(track: AudioTrack, owner: User) : this(track, owner.idLong) - public QueuedTrack(AudioTrack track, User owner) { - this(track, owner.getIdLong()); + init { + track.userData = owner } - public QueuedTrack(AudioTrack track, long owner) { - this.track = track; - this.track.setUserData(owner); - } - - @Override - public long getIdentifier() { - return track.getUserData(Long.class); - } - - public AudioTrack getTrack() { - return track; - } -} + override val identifier: Long + get() = track.getUserData(Long::class.java) +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/audio/VoiceCreation.kt b/src/main/java/dev/cosgy/textToSpeak/audio/VoiceCreation.kt new file mode 100644 index 0000000..2101a76 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/audio/VoiceCreation.kt @@ -0,0 +1,204 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.audio + +import com.ibm.icu.text.Transliterator +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.settings.UserSettings +import net.dv8tion.jda.api.entities.* +import org.apache.commons.io.FileUtils +import org.slf4j.LoggerFactory +import java.io.* +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.text.BreakIterator +import java.util.* + +class VoiceCreation( // 各種設定の値を保持するためのフィールド + private val bot: Bot +) { + private val dictionary: String? = bot.config.dictionary + private val voiceDirectory: String? = bot.config.voiceDirectory + private val winJTalkDir: String? = bot.config.winJTalkDir + private val maxMessageCount: Int = bot.config.maxMessageCount + + @Throws(IOException::class, InterruptedException::class) + fun createVoice(guild: Guild, user: User, message: String): String { + // ファイル名やパスの生成に使用するIDを生成する + val guildId = guild.id + val fileId = UUID.randomUUID().toString() + val fileName = "wav" + File.separator + guildId + File.separator + fileId + ".wav" + + // 必要なディレクトリを作成する + createDirectories(guildId) + + // ユーザーの設定を取得する + val settings = bot.userSettingsManager.getSettings(user.idLong) + + // 辞書データを取得し、メッセージを変換する + val words = bot.dictionary?.getWords(guild.idLong) + var dicMsg = sanitizeMessage(message) + for ((key, value) in words!!) { + dicMsg = dicMsg.replace(key!!.toRegex(), value!!) + } + + toKatakanaIfEnglishExists(dicMsg) + + val tmpFilePath = createTmpTextFile(guildId, fileId, dicMsg.replace("\n", "")) + + + // コマンドを生成して実行する + val command = getCommand(settings, tmpFilePath, fileName) + val builder = ProcessBuilder(*command) + builder.redirectErrorStream(true) + logger.debug("Command: " + java.lang.String.join(" ", *command)) + val process = builder.start() + process.waitFor() + return fileName + } + + // 英単語をカタカナに変換するメソッド + private fun toKatakanaIfEnglishExists(message: String): String { + var englishExists = false + for (c in message) { + if (c in 'a'..'z' || c in 'A'..'Z') { + englishExists = true + break + } + } + return if (englishExists) { + val latinToKatakana = Transliterator.getInstance("Latin-Katakana") + latinToKatakana.transliterate(message) + } else { + message + } + } + + // メッセージをサニタイズするメソッド + private fun sanitizeMessage(message: String): String { + var sanitizedMsg = message.replace("[\\uD800-\\uDFFF]".toRegex(), " ") + sanitizedMsg = sanitizedMsg.replace("Kosugi_kun".toRegex(), "コスギクン") + val sentences = BreakIterator.getSentenceInstance(Locale.JAPANESE) + sentences.setText(sanitizedMsg) + var messageCount = 0 + var lastIndex = 0 + val builder = StringBuilder() + while (sentences.next() != BreakIterator.DONE) { + val sentence = sanitizedMsg.substring(lastIndex, sentences.current()) + if (maxMessageCount > 0 && sentence.length + builder.length > maxMessageCount) { + builder.append("以下略") + break + } + builder.append(sentence) + builder.append("\n") + messageCount++ + lastIndex = sentences.current() + } + return builder.toString() + } + + // テキストファイルを作成するメソッド + @Throws(FileNotFoundException::class, UnsupportedEncodingException::class) + private fun createTmpTextFile(guildId: String, fileId: String, message: String): String { + val filePath = Paths.get("tmp", guildId, "$fileId.txt").toString() + PrintWriter(filePath, characterCode).use { it.write(message) } + return filePath + } + + private val characterCode: String + // 文字コードを取得するメソッド + get() { + if (!IS_WINDOWS) return "UTF-8" + + return if (bot.config.isForceUTF8) "UTF-8" else "Shift-JIS" + } + + // コマンドを生成するメソッド + private fun getCommand(settings: UserSettings?, tmpFilePath: String, fileName: String): Array { + val command = ArrayList() + command.add(openJTalkExecutable) + command.add("-x") + command.add(dictionary) + command.add("-m") + command.add(getVoiceFilePath(settings!!.voiceSetting)) + command.add("-ow") + command.add(fileName) + command.add("-r") + command.add(settings.speedSetting.toString()) + command.add("-jf") + command.add(settings.intonationSetting.toString()) + command.add("-a") + command.add(settings.voiceQualityASetting.toString()) + command.add("-fm") + command.add(settings.voiceQualityFmSetting.toString()) + command.add(tmpFilePath) + return command.toTypedArray() + } + + private val openJTalkExecutable: String? + get() = if (IS_WINDOWS) { + winJTalkDir?.let { Paths.get(it, "open_jtalk.exe").toString() } + } else { + "open_jtalk" + } + + private fun getVoiceFilePath(voice: String?): String? { + return voiceDirectory?.let { Paths.get(it, "$voice.htsvoice").toString() } + } + + // 必要なディレクトリを作成するメソッド + + @Throws(IOException::class) + private fun createDirectories(guildId: String) { + val tmpPath: Path = Paths.get("tmp", guildId) + val wavPath: Path = Paths.get("wav", guildId) + Files.createDirectories(tmpPath) + Files.createDirectories(wavPath) + } + + // ギルドに関連する一時ファイルや音声ファイルを削除するメソッド + @Throws(IOException::class) + fun clearGuildFolder(guild: Guild) { + val guildId = guild.id + val tmpPath = Paths.get("tmp" + File.separator + guildId) + val wavPath = Paths.get("wav" + File.separator + guildId) + if (Files.exists(tmpPath)) { + FileUtils.cleanDirectory(tmpPath.toFile()) + logger.info("Cleared temporary files for guild: $guildId") + } + if (Files.exists(wavPath)) { + FileUtils.cleanDirectory(wavPath.toFile()) + logger.info("Cleared WAV files for guild: $guildId") + } + } + + val voices: List + get() { + val voiceDir = File(requireNotNull(voiceDirectory) { "voiceDirectory is null" }) + return voiceDir.listFiles { _, name -> name.endsWith(".htsvoice") } + ?.map { file -> file.nameWithoutExtension } + ?.toList() + .orEmpty() + .also { logger.debug("Available voices: {}", it) } + } + + + companion object { + private val logger = LoggerFactory.getLogger(VoiceCreation::class.java) + private val IS_WINDOWS = System.getProperty("os.name").lowercase(Locale.getDefault()).startsWith("win") + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/owner/ShutdownCmd.java b/src/main/java/dev/cosgy/textToSpeak/commands/AdminCommand.kt similarity index 55% rename from src/main/java/dev/cosgy/TextToSpeak/commands/owner/ShutdownCmd.java rename to src/main/java/dev/cosgy/textToSpeak/commands/AdminCommand.kt index 85b4d60..e7c79ae 100644 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/owner/ShutdownCmd.java +++ b/src/main/java/dev/cosgy/textToSpeak/commands/AdminCommand.kt @@ -13,33 +13,30 @@ // See the License for the specific language governing permissions and / // limitations under the License. / ////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands -package dev.cosgy.TextToSpeak.commands.owner; +import com.jagrosh.jdautilities.command.CommandClient +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import net.dv8tion.jda.api.Permission +import java.util.function.Predicate -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.SlashCommandEvent; -import dev.cosgy.TextToSpeak.Bot; -import dev.cosgy.TextToSpeak.commands.OwnerCommand; - -public class ShutdownCmd extends OwnerCommand { - private final Bot bot; - - public ShutdownCmd(Bot bot) { - this.bot = bot; - this.name = "shutdown"; - this.help = "一時ファイルを削除してボットを停止します。"; - this.guildOnly = false; - } - - @Override - protected void execute(SlashCommandEvent event) { - event.reply(event.getClient().getWarning() + "シャットダウンしています...").queue(); - bot.shutdown(); +abstract class AdminCommand : SlashCommand() { + init { + this.category = Category("管理", Predicate { event: CommandEvent -> + if (event.isOwner || event.member.isOwner) return@Predicate true + if (event.guild == null) return@Predicate true + event.member.hasPermission(Permission.MANAGE_SERVER) + }) + guildOnly = true + userPermissions = arrayOf(Permission.ADMINISTRATOR) } - @Override - protected void execute(CommandEvent event) { - event.reply(event.getClient().getWarning() + "シャットダウンしています..."); - bot.shutdown(); + companion object { + fun checkAdminPermission(client: CommandClient, event: SlashCommandEvent): Boolean { + if (event.user.id == client.ownerId || event.member!!.isOwner) return true + return if (event.guild == null) true else event.member!!.hasPermission(Permission.MANAGE_SERVER) + } } -} +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/commands/OwnerCommand.java b/src/main/java/dev/cosgy/textToSpeak/commands/OwnerCommand.kt similarity index 83% rename from src/main/java/dev/cosgy/TextToSpeak/commands/OwnerCommand.java rename to src/main/java/dev/cosgy/textToSpeak/commands/OwnerCommand.kt index 6d2ef3d..bf0fb9e 100644 --- a/src/main/java/dev/cosgy/TextToSpeak/commands/OwnerCommand.java +++ b/src/main/java/dev/cosgy/textToSpeak/commands/OwnerCommand.kt @@ -13,14 +13,13 @@ // See the License for the specific language governing permissions and / // limitations under the License. / ////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands -package dev.cosgy.TextToSpeak.commands; +import com.jagrosh.jdautilities.command.SlashCommand -import com.jagrosh.jdautilities.command.SlashCommand; - -public abstract class OwnerCommand extends SlashCommand { - public OwnerCommand() { - this.category = new Category("Owner"); - this.ownerCommand = true; +abstract class OwnerCommand : SlashCommand() { + init { + this.category = Category("Owner") + ownerCommand = true } -} +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/admin/GuildSettings.kt b/src/main/java/dev/cosgy/textToSpeak/commands/admin/GuildSettings.kt new file mode 100644 index 0000000..b5a392b --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/admin/GuildSettings.kt @@ -0,0 +1,78 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.admin + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.commands.AdminCommand +import net.dv8tion.jda.api.EmbedBuilder +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.awt.Color + +class GuildSettings(private val bot: Bot) : AdminCommand() { + var log: Logger = LoggerFactory.getLogger(this.javaClass) + + init { + name = "gsettings" + help = "ギルドの現在の設定を確認できます。" + } + + override fun execute(event: SlashCommandEvent) { + if (!checkAdminPermission(event.client, event)) { + event.reply(event.client.warning + "権限がないため実行できません。").queue() + return + } + val settings = bot.settingsManager.getSettings(event.guild!!) + var text = "null" + if (settings.getTextChannel(event.guild) != null) { + text = settings.getTextChannel(event.guild)!!.name + } + val builder = EmbedBuilder() + .setColor(Color.orange) + .setTitle(event.guild!!.name + "の設定") + .addField("ユーザー名読み上げ:", if (settings.isReadName()) "有効" else "無効", false) + .addField( + "参加、退出時の読み上げ:", + if (settings.isJoinAndLeaveRead()) "有効" else "無効", + false + ) + .addField("読み上げるチャンネル:", text, false) + .addField("読み上げの主音量:", settings.volume.toString(), false) + event.replyEmbeds(builder.build()).queue() + } + + override fun execute(event: CommandEvent) { + val settings = bot.settingsManager.getSettings(event.guild) + var text = "null" + if (settings.getTextChannel(event.guild) != null) { + text = settings.getTextChannel(event.guild)!!.name + } + val builder = EmbedBuilder() + .setColor(Color.orange) + .setTitle(event.guild.name + "の設定") + .addField("ユーザー名読み上げ:", if (settings.isReadName()) "有効" else "無効", false) + .addField( + "参加、退出時の読み上げ:", + if (settings.isJoinAndLeaveRead()) "有効" else "無効", + false + ) //.addField("接頭語:", settings.getPrefix(), false) + .addField("読み上げるチャンネル:", text, false) + .addField("読み上げの主音量:", settings.volume.toString(), false) + event.reply(builder.build()) + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/admin/JLReadCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/admin/JLReadCmd.kt new file mode 100644 index 0000000..ce31623 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/admin/JLReadCmd.kt @@ -0,0 +1,67 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.admin + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.commands.AdminCommand +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class JLReadCmd(private val bot: Bot) : AdminCommand() { + var log: Logger = LoggerFactory.getLogger(this.javaClass) + + init { + name = "jlread" + help = "ボイスチャンネルにユーザーが参加または退出した時にユーザー名を読み上げるか否かを設定します。" + + options = listOf(OptionData(OptionType.BOOLEAN, "value", "機能を有効にするか否か", false)) + } + + override fun execute(event: SlashCommandEvent) { + if (!checkAdminPermission(event.client, event)) { + event.reply(event.client.warning + "権限がないため実行できません。").queue() + return + } + val settings = bot.settingsManager.getSettings(event.guild!!) + + if (event.getOption("value") == null) { + if (settings.isJoinAndLeaveRead()) { + settings.setJoinAndLeaveRead(false) + event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを無効にしました。").queue() + } else { + settings.setJoinAndLeaveRead(true) + event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを有効にしました。").queue() + } + } else { + val args = event.getOption("value")!!.asBoolean + + settings.setReadName(args) + + event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを${if (args) "有効" else "無効"}にしました。").queue() + } + } + + override fun execute(event: CommandEvent) { + val settings = bot.settingsManager.getSettings(event.guild) + + settings.setJoinAndLeaveRead(!settings.isJoinAndLeaveRead()) + event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを${if (settings.isJoinAndLeaveRead()) "有効" else "無効"}にしました。") + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/admin/SetReadNameCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/admin/SetReadNameCmd.kt new file mode 100644 index 0000000..4206a3c --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/admin/SetReadNameCmd.kt @@ -0,0 +1,63 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.admin + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.commands.AdminCommand +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class SetReadNameCmd(private val bot: Bot) : AdminCommand() { + var log: Logger = LoggerFactory.getLogger(this.javaClass) + + init { + name = "setreadname" + help = "テキストを読み上げる際にユーザー名も読み上げるかを設定します。" + + options = listOf(OptionData(OptionType.BOOLEAN, "value", "機能を有効にするか否か", false)) + } + + override fun execute(event: SlashCommandEvent) { + if (!checkAdminPermission(event.client, event)) { + event.reply("${event.client.warning}権限がないため実行できません。").queue() + return + } + + val settings = bot.settingsManager.getSettings(event.guild!!) + + if (event.getOption("value") == null) { + settings.setReadName(!settings.isReadName()) + event.reply("ユーザー名の読み上げを${if (settings.isReadName()) "有効" else "無効"}にしました。").queue() + } else { + val args = event.getOption("value")!!.asBoolean + + settings.setReadName(args) + + event.reply("ユーザー名の読み上げを${if (args) "有効" else "無効"}にしました。").queue() + } + } + + override fun execute(event: CommandEvent) { + val settings = bot.settingsManager.getSettings(event.guild) + + settings.setReadName(!settings.isReadName()) + event.reply("ユーザー名の読み上げを${if (settings.isReadName()) "有効" else "無効"}にしました。") + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/admin/SettcCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/admin/SettcCmd.kt new file mode 100644 index 0000000..81bc898 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/admin/SettcCmd.kt @@ -0,0 +1,109 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.admin + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import com.jagrosh.jdautilities.commons.utils.FinderUtil +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.commands.AdminCommand +import dev.cosgy.textToSpeak.settings.Settings +import dev.cosgy.textToSpeak.utils.FormatUtil +import net.dv8tion.jda.api.entities.channel.ChannelType +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.* + +class SettcCmd(bot: Bot?) : AdminCommand() { + var log: Logger = LoggerFactory.getLogger(this.javaClass) + + init { + name = "settc" + help = "読み上げをするチャンネルを設定します。読み上げするチャンネルを設定していない場合は、joinコマンドを最後に実行したチャンネルが読み上げ対象になります。" + arguments = "<チャンネル名|NONE|なし>" + children = arrayOf(Set(), None()) + } + + override fun execute(event: SlashCommandEvent) {} + override fun execute(event: CommandEvent) { + if (event.args.isEmpty()) { + event.reply("${event.client.error}チャンネルまたはNONEを含めてください。") + return + } + val s = event.client.getSettingsFor(event.guild) + if (event.args.lowercase(Locale.getDefault()).matches("(none|なし)".toRegex())) { + s.setTextChannel(null) + event.reply("${event.client.success}読み上げをするチャンネルの設定を無効にしました。") + } else { + val list = FinderUtil.findTextChannels(event.args, event.guild) + if (list.isEmpty()) event.reply("${event.client.warning}一致するチャンネルが見つかりませんでした ${event.args}") else if (list.size > 1) event.reply( + event.client.warning + FormatUtil.listOfTChannels(list, event.args) + ) else { + s.setTextChannel(list[0]) + log.info("読み上げを行うチャンネルを設定しました。") + event.reply("${event.client.success}読み上げるチャンネルを<#${list[0].id}>に設定しました。") + } + } + } + + private class Set : AdminCommand() { + init { + name = "set" + help = "読み上げるチャンネルを設定" + val options: MutableList = ArrayList() + options.add(OptionData(OptionType.CHANNEL, "channel", "テキストチャンネル", true)) + this.options = options + } + + override fun execute(event: SlashCommandEvent) { + val s = event.client.getSettingsFor(event.guild) + if (event.getOption("channel")!!.channelType != ChannelType.TEXT) { + event.reply("${event.client.error}テキストチャンネルを設定して下さい。").queue() + return + } + val channelId = event.getOption("channel")!!.asLong + val tc = event.guild!!.getTextChannelById(channelId) + s.setTextChannel(tc) + event.reply("${event.client.success}読み上げるチャンネルを<#${tc!!.id}>に設定しました。").queue() + } + } + + private class None : AdminCommand() { + init { + name = "none" + help = "読み上げるチャンネル設定をリセットします。" + } + + override fun execute(event: SlashCommandEvent) { + if (!checkAdminPermission(event.client, event)) { + event.reply("${event.client.warning}権限がないため実行できません。").queue() + return + } + val s = event.client.getSettingsFor(event.guild) + s.setTextChannel(null) + event.reply("${event.client.success}読み上げるチャンネル設定をリセットしました。").queue() + } + + override fun execute(event: CommandEvent) { + val s = event.client.getSettingsFor(event.guild) + s.setTextChannel(null) + event.reply("${event.client.success}読み上げるチャンネル設定をリセットしました。") + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/AddWordCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/AddWordCmd.kt new file mode 100644 index 0000000..3ae8777 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/AddWordCmd.kt @@ -0,0 +1,107 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.dictionary + +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import com.jagrosh.jdautilities.menu.ButtonMenu +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.entities.Message +import net.dv8tion.jda.api.entities.emoji.Emoji +import net.dv8tion.jda.api.exceptions.PermissionException +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import java.awt.Color +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern + +class AddWordCmd(private val bot: Bot) : SlashCommand() { + init { + name = "wdad" + help = "辞書に単語を追加します。辞書に単語が存在している場合は上書きされます。" + this.category = Category("辞書") + val options: MutableList = ArrayList() + options.add(OptionData(OptionType.STRING, "word", "単語", true)) + options.add(OptionData(OptionType.STRING, "reading", "読み方(カタカナ)", true)) + this.options = options + } + + private fun handleCommand(event: SlashCommandEvent, word: String, reading: String) { + val guildId = event.guild!!.idLong + val dictionary = bot.dictionary + val isWordExist = dictionary!!.getWords(guildId).containsKey(word) + event.deferReply().queue() + if (isWordExist) { + val no = "❌" + val ok = "✔" + ButtonMenu.Builder() + .setText("単語が既に存在します。上書きしますか?") + .addChoices(no, ok) + .setEventWaiter(bot.waiter) + .setTimeout(30, TimeUnit.SECONDS) + .setAction { re: Emoji -> + if (re.name == ok) { + dictionary.updateDictionary(guildId, word, reading) + sendSuccessMessage(event) + } else { + + event.hook.sendMessage("辞書登録をキャンセルしました。").queue() + } + }.setFinalAction { m: Message -> + try { + m.clearReactions().queue() + m.delete().queue() + } catch (ignore: PermissionException) { + } + }.build().display(event.messageChannel) + } else { + dictionary.updateDictionary(guildId, word, reading) + sendSuccessMessage(event) + } + } + + private fun sendSuccessMessage(event: SlashCommandEvent) { + val builder = EmbedBuilder() + .setColor(SUCCESS_COLOR) + .setTitle("単語を追加しました。") + .addField("単語", "```${event.getOption("word")!!.asString}```", false) + .addField("読み", "```${event.getOption("reading")!!.asString}```", false) + event.hook.sendMessageEmbeds(builder.build()).queue() + } + + override fun execute(event: SlashCommandEvent) { + val word = event.getOption("word")!!.asString + val reading = event.getOption("reading")!!.asString + if (!isKatakana(reading)) { + event.reply("読み方はすべてカタカナで入力して下さい。").setEphemeral(true).queue() + return + } + handleCommand(event, word, reading) + } + + companion object { + private val SUCCESS_COLOR = Color(0, 163, 129) + + //private val ERROR_COLOR = Color.RED + //private const val INVALID_ARGS_MESSAGE = "コマンドが無効です。単語と読み方の2つを入力して実行して下さい。" + //private const val USAGE_MESSAGE = "使用方法: /addword <単語> <読み方>" + private const val KATAKANA_REGEX = "^[ァ-ヶー]*$" + private fun isKatakana(str: String): Boolean { + return Pattern.matches(KATAKANA_REGEX, str) + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/DlWordCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/DlWordCmd.kt new file mode 100644 index 0000000..04dd2aa --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/DlWordCmd.kt @@ -0,0 +1,74 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.dictionary + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData + +class DlWordCmd(private val bot: Bot) : SlashCommand() { + + init { + name = "wddl" + help = "辞書に登録されている単語を削除します。" + this.category = Category("辞書") + val options: MutableList = ArrayList() + options.add(OptionData(OptionType.STRING, "word", "単語", true)) + this.options = options + } + + override fun execute(event: SlashCommandEvent) { + val words = bot.dictionary?.getWords(event.guild!!.idLong) + val args = event.getOption("word")!!.asString + if (!words!!.containsKey(args)) { + event.reply("${args}は、辞書に登録されていません。").queue() + return + } + val result = bot.dictionary?.deleteDictionary(event.guild!!.idLong, args) + if (result == true) { + event.reply("単語(${args})を削除しました。").queue() + } else { + event.reply("削除中に問題が発生しました。").setEphemeral(true).queue() + } + } + + override fun execute(event: CommandEvent) { + if (event.args.isEmpty() && event.message.attachments.isEmpty()) { + val builder = EmbedBuilder() + .setTitle("dlwordコマンド") + .addField("使用方法:", "$name <単語>", false) + .addField("説明:", help, false) + event.reply(builder.build()) + return + } + val words = bot.dictionary?.getWords(event.guild.idLong) + val args = event.args + if (!words!!.containsKey(args)) { + event.reply("${args}は、辞書に登録されていません。") + return + } + val result = bot.dictionary?.deleteDictionary(event.guild.idLong, args) + if (result == true) { + event.reply("単語(${args})を削除しました。") + } else { + event.reply("削除中に問題が発生しました。") + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/WordListCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/WordListCmd.kt new file mode 100644 index 0000000..b25d10b --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/dictionary/WordListCmd.kt @@ -0,0 +1,98 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.dictionary + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import com.jagrosh.jdautilities.menu.Paginator +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.Message +import net.dv8tion.jda.api.exceptions.PermissionException +import net.dv8tion.jda.api.interactions.InteractionHook +import java.util.concurrent.TimeUnit +import java.util.stream.Collectors + +class WordListCmd(private val bot: Bot) : SlashCommand() { + private val builder: Paginator.Builder + + init { + name = "wdls" + help = "辞書に登録してある単語をリストアップします。" + this.category = Category("辞書") + botPermissions = arrayOf(Permission.MESSAGE_ADD_REACTION, Permission.MESSAGE_EMBED_LINKS) + builder = Paginator.Builder() + .setColumns(1) + .setFinalAction { m: Message -> + try { + m.clearReactions().queue() + } catch (ignore: PermissionException) { + } + } + .setItemsPerPage(10) + .waitOnSinglePage(false) + .useNumberedItems(true) + .showPageNumbers(true) + .wrapPageEnds(true) + .setEventWaiter(bot.waiter) + .setTimeout(1, TimeUnit.MINUTES) + } + + override fun execute(event: SlashCommandEvent) { + event.reply("単語一覧を表示します。").queue { m: InteractionHook -> + val wordList = bot.dictionary?.getWords(event.guild!!.idLong) + ?.entries?.stream() + ?.map { (key, value): Map.Entry -> "$key-$value" } + ?.collect(Collectors.toList()) + if (wordList != null) { + if (wordList.isEmpty()) { + m.editOriginal("単語が登録されていません。").queue() + return@queue + } + } + m.deleteOriginal().queue() + builder.setText("単語一覧") + .setItems(*wordList!!.toTypedArray()) + .setUsers(event.user) + .setColor(event.guild!!.selfMember.color) + builder.build().paginate(event.channel, 1) + } + } + + override fun execute(event: CommandEvent) { + var pagenum = 1 + try { + pagenum = event.args.toInt() + } catch (ignore: NumberFormatException) { + } + val wordList = bot.dictionary?.getWords(event.guild.idLong) + ?.entries?.stream() + ?.map { (key, value): Map.Entry -> "$key-$value" } + ?.collect(Collectors.toList()) + if (wordList != null) { + if (wordList.isEmpty()) { + event.reply("単語が登録されていません。") + return + } + } + builder.setText("単語一覧") + .setItems(*wordList!!.toTypedArray()) + .setUsers(event.author) + .setColor(event.selfMember.color) + builder.build().paginate(event.channel, pagenum) + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/AboutCommand.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/AboutCommand.kt new file mode 100644 index 0000000..7e5b1af --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/AboutCommand.kt @@ -0,0 +1,151 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandClient +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import com.jagrosh.jdautilities.commons.JDAUtilitiesInfo +import com.jagrosh.jdautilities.doc.standard.CommandInfo +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.JDA +import net.dv8tion.jda.api.JDAInfo +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.Guild +import net.dv8tion.jda.api.entities.channel.ChannelType +import org.slf4j.LoggerFactory +import java.awt.Color +import java.util.* + +/** + * @author Kosugi_kun + */ +@CommandInfo(name = ["About"], description = "ボットに関する情報を表示します") +class AboutCommand(private val color: Color, private val description: String, vararg perms: Permission) : + SlashCommand() { + private val perms: Array + private var isAuthor = true + private var replacementIcon = "+" + private var oauthLink: String? = null + + init { + name = "about" + help = "ボットに関する情報を表示します" + guildOnly = false + this.perms = perms + botPermissions = arrayOf(Permission.MESSAGE_EMBED_LINKS) + } + + fun setIsAuthor(value: Boolean) { + isAuthor = value + } + + fun setReplacementCharacter(value: String) { + replacementIcon = value + } + + override fun execute(event: SlashCommandEvent) { + getOauthLink(event.jda) + val builder = EmbedBuilder() + builder.setColor(if (event.isFromType(ChannelType.TEXT)) event.guild!!.selfMember.color else color) + builder.setAuthor(event.jda.selfUser.name + "について!", null, event.jda.selfUser.avatarUrl) + val cosgyOwner = "Cosgy Devが運営、開発をしています。" + val author = + if (event.jda.getUserById(event.client.ownerId) == null) "<@" + event.client.ownerId + ">" else Objects.requireNonNull( + event.jda.getUserById(event.client.ownerId) + )?.name + val descr = StringBuilder().append("こんにちは! **").append(event.jda.selfUser.name).append("**です。 ") + .append(description).append("は、") + .append(JDAUtilitiesInfo.AUTHOR + "の[コマンド拡張](" + JDAUtilitiesInfo.GITHUB + ") (") + .append(JDAUtilitiesInfo.VERSION).append(")と[JDAライブラリ](https://github.com/DV8FromTheWorld/JDA) (") + .append(JDAInfo.VERSION).append(")を使用しており、").append(if (isAuthor) cosgyOwner else author + "が所有しています。") + .append(event.jda.selfUser.name) + .append("についての質問などは[Cosgy Dev公式チャンネル](https://discord.gg/RBpkHxf)へお願いします。") + .append("\nこのボットの使用方法は`").append(event.client.textualPrefix).append(event.client.helpWord) + .append("`で確認することができます。") + getMessage(builder, descr, event.jda, event.client) + event.replyEmbeds(builder.build()).queue() + } + + private fun getMessage(builder: EmbedBuilder, descr: StringBuilder, jda: JDA, client: CommandClient) { + builder.setDescription(descr) + if (jda.shardInfo.shardTotal == 1) { + builder.addField( + "ステータス", """${jda.guilds.size} サーバー + |1シャード""".trimMargin(), true + ) + builder.addField( + "ユーザー", """${jda.users.size} ユニーク + |${jda.guilds.stream().mapToInt { g: Guild -> g.members.size }.sum()} 合計""".trimMargin(), true + ) + builder.addField( + "チャンネル", """${jda.textChannels.size} テキスト + |${jda.voiceChannels.size} ボイス""".trimMargin(), true + ) + } else { + builder.addField( + "ステータス", """${client.totalGuilds} サーバー + |シャード ${jda.shardInfo.shardId + 1}/${jda.shardInfo.shardTotal}""".trimMargin(), true + ) + builder.addField( + "", """${jda.users.size} ユーザーのシャード + |${jda.guilds.size} サーバー""".trimMargin(), true + ) + builder.addField( + "", """${jda.textChannels.size} テキストチャンネル + |${jda.voiceChannels.size} ボイスチャンネル""".trimMargin(), true + ) + } + builder.setFooter("再起動が行われた時間") + builder.setTimestamp(client.startTime) + } + + override fun execute(event: CommandEvent) { + getOauthLink(event.jda) + val builder = EmbedBuilder() + builder.setColor(if (event.isFromType(ChannelType.TEXT)) event.guild.selfMember.color else color) + builder.setAuthor(event.selfUser.name + "について!", null, event.selfUser.avatarUrl) + val cosgyOwner = "Cosgy Devが運営、開発をしています。" + val author = + if (event.jda.getUserById(event.client.ownerId) == null) "<@" + event.client.ownerId + ">" else Objects.requireNonNull( + event.jda.getUserById(event.client.ownerId) + )?.name + val descr = StringBuilder().append("こんにちは! **").append(event.selfUser.name).append("**です。 ") + .append(description).append("は、") + .append(JDAUtilitiesInfo.AUTHOR + "の[コマンド拡張](" + JDAUtilitiesInfo.GITHUB + ") (") + .append(JDAUtilitiesInfo.VERSION).append(")と[JDAライブラリ](https://github.com/DV8FromTheWorld/JDA) (") + .append(JDAInfo.VERSION).append(")を使用しており、").append(if (isAuthor) cosgyOwner else author + "が所有しています。") + .append(event.selfUser.name).append("についての質問などは[Cosgy Dev公式チャンネル](https://discord.gg/RBpkHxf)へお願いします。") + .append("\nこのボットの使用方法は`").append(event.client.textualPrefix).append(event.client.helpWord) + .append("`で確認することができます。") + getMessage(builder, descr, event.jda, event.client) + event.reply(builder.build()) + } + + private fun getOauthLink(jda: JDA) { + if (oauthLink == null) { + oauthLink = try { + val info = jda.retrieveApplicationInfo().complete() + if (info.isBotPublic) info.getInviteUrl(0L, *perms) else "" + } catch (e: Exception) { + val log = LoggerFactory.getLogger("OAuth2") + log.error("招待リンクを生成できませんでした ", e) + "" + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/ByeCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/ByeCmd.kt new file mode 100644 index 0000000..ea9f855 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/ByeCmd.kt @@ -0,0 +1,65 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.audio.AudioHandler +import net.dv8tion.jda.api.EmbedBuilder +import java.awt.Color +import java.io.IOException + +class ByeCmd(private val bot: Bot) : SlashCommand() { + init { + name = "bye" + help = "ボイスチャンネルから退出します。" + guildOnly = true + } + + override fun execute(event: SlashCommandEvent) { + val handler = event.guild!!.audioManager.sendingHandler as AudioHandler? + handler!!.stopAndClear() + try { + bot.voiceCreation.clearGuildFolder(event.guild!!) + } catch (e: IOException) { + throw RuntimeException(e) + } + event.guild!!.audioManager.closeAudioConnection() + val builder = EmbedBuilder() + builder.setColor(Color(180, 76, 151)) + builder.setTitle("VCから切断") + builder.setDescription("ボイスチャンネルから切断しました。") + event.replyEmbeds(builder.build()).queue() + } + + override fun execute(event: CommandEvent) { + val handler = event.guild.audioManager.sendingHandler as AudioHandler? + handler!!.stopAndClear() + try { + bot.voiceCreation.clearGuildFolder(event.guild) + } catch (e: IOException) { + throw RuntimeException(e) + } + event.guild.audioManager.closeAudioConnection() + val builder = EmbedBuilder() + builder.setColor(Color(180, 76, 151)) + builder.setTitle("VCから切断") + builder.setDescription("ボイスチャンネルから切断しました。") + event.reply(builder.build()) + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/HelpCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/HelpCmd.kt new file mode 100644 index 0000000..46f94ab --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/HelpCmd.kt @@ -0,0 +1,99 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.entities.Message +import net.dv8tion.jda.api.entities.channel.ChannelType +import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel +import java.awt.Color + +class HelpCmd(var bot: Bot) : SlashCommand() { + init { + name = "help" + help = "コマンド一覧を表示します。" + } + + override fun execute(event: SlashCommandEvent) { + val eBuilder = EmbedBuilder() + eBuilder.setTitle("**" + event.jda.selfUser.name + "** コマンド一覧") + eBuilder.setColor(Color(245, 229, 107)) + val builder = StringBuilder() + var category: Category? = null + val commands = event.client.slashCommands + for (command in commands) { + if (!command.isHidden && (!command.isOwnerCommand || event.member!!.isOwner)) { + if (category != command.category) { + category = command.category + builder.append("\n\n __").append(if (category == null) "カテゴリなし" else category.name).append("__:\n") + } + builder.append("\n`").append("/").append(command.name) + .append(if (command.arguments == null) "`" else " " + command.arguments + "`") + .append(" - ").append(command.help) + } + } + if (event.client.serverInvite != null) builder.append("\n\nさらにヘルプが必要な場合は、公式サーバーに参加することもできます: ") + .append(event.client.serverInvite) + eBuilder.setDescription(builder) + if (bot.config.helpToDm) { + event.user.openPrivateChannel() + .flatMap { channel: PrivateChannel -> channel.sendMessageEmbeds(eBuilder.build()) }.queue() + } else { + event.replyEmbeds(eBuilder.build()).queue() + } + } + + public override fun execute(event: CommandEvent) { + val builder = StringBuilder( + """ + + **${event.jda.selfUser.name}** コマンド一覧: + + """.trimIndent() + ) + var category: Category? = null + val commands = event.client.commands + for (command in commands) { + if (!command.isHidden && (!command.isOwnerCommand || event.isOwner)) { + if (category != command.category) { + category = command.category + builder.append("\n\n __").append(if (category == null) "カテゴリなし" else category.name).append("__:\n") + } + builder.append("\n`").append(event.client.textualPrefix) + .append(if (event.client.prefix == null) " " else "").append(command.name) + .append(if (command.arguments == null) "`" else " " + command.arguments + "`") + .append(" - ").append(command.help) + } + } + if (event.client.serverInvite != null) builder.append("\n\nさらにヘルプが必要な場合は、公式サーバーに参加することもできます: ") + .append(event.client.serverInvite) + if (bot.config.helpToDm) { + event.replyInDm( + builder.toString(), + { _: Message? -> if (event.isFromType(ChannelType.TEXT)) event.reactSuccess() }) { _: Throwable? -> + event.replyWarning( + "ダイレクトメッセージをブロックしているため、ヘルプを送信できません。" + ) + } + } else { + event.reply(builder.toString()) + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/JoinCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/JoinCmd.kt new file mode 100644 index 0000000..c95366b --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/JoinCmd.kt @@ -0,0 +1,108 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.settings.Settings +import dev.cosgy.textToSpeak.utils.ReadChannel +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.exceptions.PermissionException +import java.awt.Color + +class JoinCmd(private var bot: Bot) : SlashCommand() { + init { + name = "join" + help = "ボイスチャンネルに参加します。" + guildOnly = true + } + + override fun execute(event: SlashCommandEvent) { + event.deferReply().queue() + val settings = event.client.getSettingsFor(event.guild) + val channel = settings.getTextChannel(event.guild) + // VoiceChannel voiceChannel = settings.getVoiceChannel(event.getGuild()); + bot.playerManager.setUpHandler(event.guild!!) + val userState = event.member!!.voiceState + val builder = EmbedBuilder() + builder.setColor(Color(76, 108, 179)) + builder.setTitle("VCに接続") + if (!userState!!.inAudioChannel() || userState.isDeafened) { + builder.setDescription(String.format("このコマンドを使用するには、%sに参加している必要があります。", "音声チャンネル")) + event.replyEmbeds(builder.build()).queue() + return + } + if (channel == null) { + builder.addField("読み上げ対象", event.channel.name, true) + } else { + builder.addField("読み上げ対象", channel.name, true) + } + try { + // ボイスチャンネル接続完了のメッセージに現在の設定を表示 + event.guild!!.audioManager.openAudioConnection(userState.channel) + builder.addField("ボイスチャンネル", String.format("**%s**", userState.channel!!.name), false) + builder.setDescription("ボイスチャンネルへの接続に成功しました。") + builder.addField( + "設定", + "ユーザー名読み上げ:${if (settings.isReadName()) "有効" else "無効"}\n" + + "参加、退出読み上げ:${if (settings.isJoinAndLeaveRead()) "有効" else "無効"}", true + ) + event.hook.sendMessageEmbeds(builder.build()).queue() + ReadChannel.setChannel(event.guild!!.idLong, event.textChannel.idLong) + } catch (ex: PermissionException) { + builder.appendDescription(event.client.error + String.format("**%s**に接続できません!", userState.channel!!.name)) + builder.addField( + "ボイスチャンネル", event.client.error + String.format( + "**%s**に接続できません!", + userState.channel!!.name + ), false + ) + event.hook.sendMessageEmbeds(builder.build()).queue() + } + } + + override fun execute(event: CommandEvent) { + val settings = event.client.getSettingsFor(event.guild) + val channel = settings.getTextChannel(event.guild) + bot.playerManager.setUpHandler(event.guild) + val userState = event.member.voiceState + val builder = EmbedBuilder() + builder.setColor(Color(76, 108, 179)) + builder.setTitle("VCに接続") + if (!userState!!.inAudioChannel()) { + builder.setDescription("このコマンドを使用するには、ボイスチャンネルに参加している必要があります。") + event.reply(builder.build()) + return + } + if (channel == null) { + builder.addField("読み上げ対象", event.channel.name, true) + } else { + builder.addField("読み上げ対象", channel.name, true) + } + try { + event.guild.audioManager.openAudioConnection(userState.channel) + builder.addField("ボイスチャンネル", String.format("**%s**", userState.channel!!.name), false) + builder.setDescription("ボイスチャンネルへの接続に成功しました。") + event.reply(builder.build()) + ReadChannel.setChannel(event.guild.idLong, event.textChannel.idLong) + } catch (ex: PermissionException) { + builder.setDescription("ボイスチャンネルへの接続に失敗しました。") + event.reply(builder.build()) + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/SetIntonationCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetIntonationCmd.kt new file mode 100644 index 0000000..bea3200 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetIntonationCmd.kt @@ -0,0 +1,70 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import java.math.BigDecimal + +class SetIntonationCmd(private val bot: Bot) : SlashCommand() { + init { + name = "setinto" + help = "F0系列内変動の重みの設定を変更します。" + guildOnly = false + category = Category("設定") + options = listOf(OptionData(OptionType.STRING, "value", "0.1~100.0", true)) + } + + override fun execute(event: SlashCommandEvent) { + val bd = event.getOption("value")?.asString?.toBigDecimalOrNull() + + if (bd == null || bd < BigDecimal.ZERO || bd > BigDecimal("100.0")) { + event.reply("有効な数値を設定してください。0.1~100.0").queue() + return + } + + val settings = bot.userSettingsManager.getSettings(event.user.idLong) + settings.intonationSetting = bd.toFloat() + event.reply("F0系列内変動の重みを${bd}に設定しました。").queue() + } + + override fun execute(event: CommandEvent) { + if (event.args.isEmpty()) { + val builder = EmbedBuilder() + .setTitle("setintoコマンド") + .addField("使用方法:", "$name <数値(0.0~)>", false) + .addField("説明:", "F0系列内変動の重みを変更します。F0系列内変動の重みは、0.0以上の数値で設定して下さい。", false) + event.reply(builder.build()) + return + } + + val bd = event.args.toBigDecimalOrNull() + + if (bd == null || bd < BigDecimal.ZERO || bd > BigDecimal("100.0")) { + event.reply("有効な数値を設定してください。0.1~100.0") + return + } + + val settings = bot.userSettingsManager.getSettings(event.author.idLong) + settings.intonationSetting = bd.toFloat() + event.reply("F0系列内変動の重みを${bd}に設定しました。") + } +} diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/SetSpeedCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetSpeedCmd.kt new file mode 100644 index 0000000..89fa572 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetSpeedCmd.kt @@ -0,0 +1,87 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import java.math.BigDecimal + +class SetSpeedCmd(private val bot: Bot) : SlashCommand() { + init { + name = "setspeed" + help = "読み上げ速度の設定を変更します。" + guildOnly = false + category = Category("設定") + options = mutableListOf(OptionData(OptionType.STRING, "value", "0.1~100.0", true)) + } + + override fun execute(event: SlashCommandEvent) { + val args = event.getOption("value")?.asString + val bd = try { + BigDecimal(args) + } catch (e: NumberFormatException) { + event.reply("数値を設定して下さい。").queue() + return + } + val min = BigDecimal.ZERO + val max = BigDecimal("100.0") + if (!(min < bd && max > bd)) { + event.reply("有効な数値を設定して下さい。0.1~100.0").queue() + return + } + val settings = bot.userSettingsManager.getSettings(event.user.idLong) + settings.speedSetting = bd.toFloat() + event.reply("速度を${bd}に設定しました。").queue() + } + + override fun execute(event: CommandEvent) { + val args = event.args + + if (args == null) { + help(event) + return + } + + val bd = try { + BigDecimal(args) + } catch (e: NumberFormatException) { + event.reply("数値を設定して下さい。") + return + } + val min = BigDecimal.ZERO + val max = BigDecimal("100.0") + if (!(min < bd && max > bd)) { + event.reply("有効な数値を設定して下さい。0.1~100.0") + return + } + val settings = bot.userSettingsManager.getSettings(event.author.idLong) + settings.speedSetting = bd.toFloat() + event.reply("速度を${bd}に設定しました。") + } + + fun help(event: CommandEvent?) { + val builder = EmbedBuilder() + .setTitle("setspeedコマンド") + .addField("使用方法:", "$name <数値(0.0~)>", false) + .addField("説明:", "読み上げの速度を設定します。読み上げ速度は、0.0以上の数値で設定して下さい。", false) + event?.reply(builder.build()) + } +} diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceCmd.kt new file mode 100644 index 0000000..482e486 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceCmd.kt @@ -0,0 +1,108 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent +import net.dv8tion.jda.api.interactions.commands.Command +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import java.util.stream.Collectors +import java.util.stream.Stream + +class SetVoiceCmd(private var bot: Bot) : SlashCommand() { + private var voices: Array + + init { + name = "setvoice" + help = "声の種類を変更することができます。" + guildOnly = false + this.category = Category("設定") + val options: MutableList = ArrayList() + options.add(OptionData(OptionType.STRING, "name", "声データの名前", true, true)) + this.options = options + voices = bot.voiceCreation.voices.toTypedArray() + } + + override fun execute(event: SlashCommandEvent) { + if (event.getOption("name") == null) { + val builder = EmbedBuilder() + .setTitle("setvoiceコマンド") + .addField("声データ一覧:", voices.contentToString(), false) + .addField("使用方法:", "$name <声データの名前>", false) + event.replyEmbeds(builder.build()).queue() + return + } + val voiceName = event.getOption("name")!!.asString + if (isValidVoice(voiceName)) { + val settings = bot.userSettingsManager.getSettings(event.user.idLong) + settings.voiceSetting = voiceName + event.reply("声データを`$voiceName`に設定しました。").queue() + } else { + event.reply("有効な声データを選択して下さい。").queue() + } + } + + override fun execute(event: CommandEvent) { + val voices = bot.voiceCreation.voices + if (event.args.isEmpty() && event.message.attachments.isEmpty()) { + val builder = EmbedBuilder() + .setTitle("setvoiceコマンド") + .addField("声データ一覧:", voices.toString(), false) + .addField("使用方法:", "$name <声データの名前>", false) + event.reply(builder.build()) + return + } + val args = event.args + if (voices.contains(args)) { + val settings = bot.userSettingsManager.getSettings(event.author.idLong) + settings.voiceSetting = args + event.reply("声データを`$args`に設定しました。") + } else { + event.reply("有効な声データを選択して下さい。") + } + } + + override fun onAutoComplete(event: CommandAutoCompleteInteractionEvent) { + if (event.name == "setvoice" && event.focusedOption.name == "name") { + val options = Stream.of(*voices) + .filter { word: String? -> word!!.startsWith(event.focusedOption.value) } // only display words that start with the user's current input + .map { word: String? -> Command.Choice(word!!, word) } // map the words to choices + .collect(Collectors.toList()) + event.replyChoices(options).queue() + } + super.onAutoComplete(event) + } + + /** + * ユーザーが入力した声の名前が有効かを確認 + * + * @param voice ユーザーが入力した声の名前 + * @return 名前が有効の場合は true 無効な場合は false + */ + private fun isValidVoice(voice: String): Boolean { + for (v in voices) { + if (v == voice) { + return true + } + } + return false + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceQualityA.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceQualityA.kt new file mode 100644 index 0000000..c76f963 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceQualityA.kt @@ -0,0 +1,90 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import java.math.BigDecimal + +class SetVoiceQualityA(private var bot: Bot) : SlashCommand() { + init { + name = "setqa" + help = "オールパス値の設定を変更します。" + guildOnly = false + this.category = Category("設定") + val options: MutableList = ArrayList() + options.add(OptionData(OptionType.STRING, "value", "0.1~1.0", true)) + this.options = options + } + + override fun execute(event: SlashCommandEvent) { + val args = event.getOption("value")!!.asString + var result: Boolean + var bd: BigDecimal? = null + try { + //value = Float.parseFloat(args); + bd = BigDecimal(args) + result = true + } catch (e: NumberFormatException) { + result = false + } + if (!result) { + event.reply("数値を設定して下さい。").queue() + return + } + val min = BigDecimal("0.0") + val max = BigDecimal("1.0") + + //if(!(0.1f <= value && value <= 1.0f)){ + if (!(min < bd && max > bd)) { + event.reply("有効な数値を設定して下さい。0.1~1.0").queue() + return + } + val settings = bot.userSettingsManager.getSettings(event.user.idLong) + settings.voiceQualityASetting = bd!!.toFloat() + event.reply("オールパス値を${bd}に設定しました。").queue() + } + + override fun execute(event: CommandEvent) { + val args = event.args + var result: Boolean + var bd: BigDecimal? = null + try { + bd = BigDecimal(args) + result = true + } catch (e: NumberFormatException) { + result = false + } + if (!result) { + event.reply("数値を設定して下さい。") + return + } + val min = BigDecimal("0.0") + val max = BigDecimal("1.0") + + if (!(min < bd && max > bd)) { + event.reply("有効な数値を設定して下さい。0.1~1.0") + return + } + val settings = bot.userSettingsManager.getSettings(event.author.idLong) + settings.voiceQualityASetting = bd!!.toFloat() + event.reply("オールパス値を${bd}に設定しました。") + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceQualityFm.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceQualityFm.kt new file mode 100644 index 0000000..397a28a --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceQualityFm.kt @@ -0,0 +1,101 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import java.math.BigDecimal + +class SetVoiceQualityFm(private var bot: Bot) : SlashCommand() { + init { + name = "setqfm" + help = "追加ハーフトーンの設定を変更します。" + guildOnly = false + this.category = Category("設定") + val options: MutableList = ArrayList() + options.add(OptionData(OptionType.STRING, "value", "0.1~100.0", true)) + this.options = options + } + + override fun execute(event: SlashCommandEvent) { + val args = event.getOption("value")!!.asString + var result: Boolean + var bd: BigDecimal? = null + try { + //value = Float.parseFloat(args); + bd = BigDecimal(args) + result = true + } catch (e: NumberFormatException) { + result = false + } + if (!result) { + event.reply("数値を設定して下さい。").queue() + return + } + val min = BigDecimal("0.0") + val max = BigDecimal("100.0") + + //if(!(0.1f <= value && value <= 100.0f)){ + if (!(min < bd && max > bd)) { + event.reply("有効な数値を設定して下さい。0.1~100.0").queue() + return + } + val settings = bot.userSettingsManager.getSettings(event.user.idLong) + bd?.let { settings.voiceQualityFmSetting = it.toFloat() } + event.reply("追加ハーフトーンを${bd}に設定しました。").queue() + } + + override fun execute(event: CommandEvent) { + if (event.args.isEmpty() && event.message.attachments.isEmpty()) { + val builder = EmbedBuilder() + .setTitle("setqfmコマンド") + .addField("使用方法:", "$name <数値(0.0~)>", false) + .addField("説明:", "追加ハーフトーンの設定を変更します。", false) + event.reply(builder.build()) + return + } + val args = event.args + var result: Boolean + var bd: BigDecimal? = null + try { + //value = Float.parseFloat(args); + bd = BigDecimal(args) + result = true + } catch (e: NumberFormatException) { + result = false + } + if (!result) { + event.reply("数値を設定して下さい。") + return + } + val min = BigDecimal("0.0") + val max = BigDecimal("100.0") + + //if(!(0.1f <= value && value <= 100.0f)){ + if (!(min < bd && max > bd)) { + event.reply("有効な数値を設定して下さい。0.1~100.0") + return + } + val settings = bot.userSettingsManager.getSettings(event.author.idLong) + bd?.let { settings.voiceQualityFmSetting = it.toFloat() } + event.reply("追加ハーフトーンを${bd}に設定しました。") + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/commands/general/SettingsCmd.kt b/src/main/java/dev/cosgy/textToSpeak/commands/general/SettingsCmd.kt new file mode 100644 index 0000000..0835153 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/commands/general/SettingsCmd.kt @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.general + +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommand +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import net.dv8tion.jda.api.EmbedBuilder +import java.awt.Color + +class SettingsCmd(private var bot: Bot) : SlashCommand() { + init { + guildOnly = false + name = "settings" + help = "現在の設定を確認します。" + this.category = Category("設定") + } + + override fun execute(event: SlashCommandEvent) { + val settings = bot.userSettingsManager.getSettings(event.user.idLong) + val builder = EmbedBuilder() + .setColor(Color.orange) + .setTitle(event.user.name + "の設定") + .addField("声:", settings.voiceSetting, false) + .addField("読み上げ速度:", settings.speedSetting.toString(), false) + .addField("F0系列内変動の重み:", settings.intonationSetting.toString(), false) + .addField("オールパス値:", settings.voiceQualityASetting.toString(), false) + .addField("追加ハーフトーン:", settings.voiceQualityFmSetting.toString(), false) + event.replyEmbeds(builder.build()).queue() + } + + override fun execute(event: CommandEvent) { + val settings = bot.userSettingsManager.getSettings(event.author.idLong) + val builder = EmbedBuilder() + .setColor(Color.orange) + .setTitle(event.author.name + "の設定") + .addField("声:", settings.voiceSetting, false) + .addField("速度:", settings.speedSetting.toString(), false) + .addField("抑揚:", settings.intonationSetting.toString(), false) + .addField("声質a:", settings.voiceQualityASetting.toString(), false) + .addField("声質fm:", settings.voiceQualityFmSetting.toString(), false) + event.reply(builder.build()) + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/gui/ConsolePanel.java b/src/main/java/dev/cosgy/textToSpeak/commands/owner/ShutdownCmd.kt similarity index 63% rename from src/main/java/dev/cosgy/TextToSpeak/gui/ConsolePanel.java rename to src/main/java/dev/cosgy/textToSpeak/commands/owner/ShutdownCmd.kt index fc18197..0c76131 100644 --- a/src/main/java/dev/cosgy/TextToSpeak/gui/ConsolePanel.java +++ b/src/main/java/dev/cosgy/textToSpeak/commands/owner/ShutdownCmd.kt @@ -13,29 +13,27 @@ // See the License for the specific language governing permissions and / // limitations under the License. / ////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.commands.owner -package dev.cosgy.TextToSpeak.gui; +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.SlashCommandEvent +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.commands.OwnerCommand -import javax.swing.*; -import java.awt.*; -import java.io.PrintStream; - -public class ConsolePanel extends JPanel { - public ConsolePanel() { - super(); - JTextArea text = new JTextArea(); - text.setLineWrap(true); - text.setWrapStyleWord(true); - text.setEditable(false); - PrintStream con = new PrintStream(new TextAreaOutputStream(text)); - System.setOut(con); - System.setErr(con); +class ShutdownCmd(private val bot: Bot) : OwnerCommand() { + init { + name = "shutdown" + help = "一時ファイルを削除してボットを停止します。" + guildOnly = false + } - JScrollPane pane = new JScrollPane(); - pane.setViewportView(text); + override fun execute(event: SlashCommandEvent) { + event.reply(event.client.warning + "シャットダウンしています...").queue() + bot.shutdown() + } - super.setLayout(new GridLayout(1, 1)); - super.add(pane); - super.setPreferredSize(new Dimension(400, 300)); + override fun execute(event: CommandEvent) { + event.reply(event.client.warning + "シャットダウンしています...") + bot.shutdown() } -} +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/entities/Prompt.kt b/src/main/java/dev/cosgy/textToSpeak/entities/Prompt.kt new file mode 100644 index 0000000..66c7478 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/entities/Prompt.kt @@ -0,0 +1,89 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.entities + +import org.slf4j.LoggerFactory +import java.util.* +import javax.swing.JOptionPane + +class Prompt @JvmOverloads constructor( + private val title: String, + noguiMessage: String? = null, + var isNoGUI: Boolean = "true".equals( + System.getProperty("nogui"), + ignoreCase = true + ) +) { + private val noguiMessage: String + private var scanner: Scanner? = null + + init { + this.noguiMessage = noguiMessage ?: "noguiモードに切り替えます。 -nogui=trueフラグを含めることで、手動でnoguiモードで起動できます。" + } + + fun alert(level: Level?, context: String?, message: String) { + if (isNoGUI) { + val log = LoggerFactory.getLogger(context) + when (level) { + Level.WARNING -> log.warn(message) + Level.ERROR -> log.error(message) + Level.INFO -> log.info(message) + else -> log.info(message) + } + } else { + try { + var option = 0 + when (level) { + Level.INFO -> option = JOptionPane.INFORMATION_MESSAGE + Level.WARNING -> option = JOptionPane.WARNING_MESSAGE + Level.ERROR -> {} + else -> option = JOptionPane.PLAIN_MESSAGE + } + JOptionPane.showMessageDialog(null, "

$message", title, option) + } catch (e: Exception) { + isNoGUI = true + alert(Level.WARNING, context, noguiMessage) + alert(level, context, message) + } + } + } + + fun prompt(content: String?): String? { + return if (isNoGUI) { + if (scanner == null) scanner = Scanner(System.`in`) + try { + println(content) + if (scanner!!.hasNextLine()) scanner!!.nextLine() else null + } catch (e: Exception) { + alert(Level.ERROR, title, "コマンドラインから入力を読み込めません。") + e.printStackTrace() + null + } + } else { + try { + JOptionPane.showInputDialog(null, content, title, JOptionPane.QUESTION_MESSAGE) + } catch (e: Exception) { + isNoGUI = true + alert(Level.WARNING, title, noguiMessage) + prompt(content) + } + } + } + + enum class Level { + INFO, WARNING, ERROR + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/gui/ConsolePanel.kt b/src/main/java/dev/cosgy/textToSpeak/gui/ConsolePanel.kt new file mode 100644 index 0000000..a5434eb --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/gui/ConsolePanel.kt @@ -0,0 +1,40 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.gui + +import java.awt.Dimension +import java.awt.GridLayout +import java.io.PrintStream +import javax.swing.JPanel +import javax.swing.JScrollPane +import javax.swing.JTextArea + +class ConsolePanel : JPanel() { + init { + val text = JTextArea() + text.lineWrap = true + text.wrapStyleWord = true + text.isEditable = false + val con = PrintStream(TextAreaOutputStream(text)) + System.setOut(con) + System.setErr(con) + val pane = JScrollPane() + pane.setViewportView(text) + super.setLayout(GridLayout(1, 1)) + super.add(pane) + super.setPreferredSize(Dimension(400, 300)) + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/gui/GUI.kt b/src/main/java/dev/cosgy/textToSpeak/gui/GUI.kt new file mode 100644 index 0000000..5fb2efe --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/gui/GUI.kt @@ -0,0 +1,255 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.gui + +import com.sun.management.OperatingSystemMXBean +import dev.cosgy.textToSpeak.Bot +import java.awt.Font +import java.awt.event.WindowEvent +import java.awt.event.WindowListener +import java.lang.management.* +import java.time.Duration +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import javax.swing.* +import kotlin.math.ln +import kotlin.math.pow +import kotlin.system.exitProcess + + +/** + * @author Kosugi_kun + */ +class GUI(private val bot: Bot) : JFrame() { + private val console: ConsolePanel = ConsolePanel() + + private var runtimeMx: RuntimeMXBean? = null + private var compilationMx: CompilationMXBean? = null + private var sunThreadMx: ThreadMXBean? = null + private var memoryMx: MemoryMXBean? = null + private var classLoadingMx: ClassLoadingMXBean? = null + private var sunOsMx: OperatingSystemMXBean? = null + private var garbageCollectors: Collection? = null + + fun init() { + + runtimeMx = ManagementFactory.getRuntimeMXBean() + compilationMx = ManagementFactory.getCompilationMXBean() + sunThreadMx = ManagementFactory.getThreadMXBean() as com.sun.management.ThreadMXBean + memoryMx = ManagementFactory.getMemoryMXBean() + classLoadingMx = ManagementFactory.getClassLoadingMXBean() + sunOsMx = ManagementFactory.getOperatingSystemMXBean() as OperatingSystemMXBean + garbageCollectors = ManagementFactory.getGarbageCollectorMXBeans().filterIsInstance() + + + defaultCloseOperation = EXIT_ON_CLOSE + title = "TextToSpeak Bot by Cosgy Dev" + val tabs = JTabbedPane() + tabs.add("コンソール", console) + + val botInfoPanel = JPanel() + + botInfoPanel.layout = BoxLayout(botInfoPanel, BoxLayout.Y_AXIS) + val scrollPane = + JScrollPane(botInfoPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED) + + // Add bot info label to bot info panel + val botInfoLabel = JLabel() + + val osName = sunOsMx?.name + val osVersion = sunOsMx?.version + val osArch = sunOsMx?.arch + val processors = sunOsMx?.availableProcessors + + //val vmName = runtimeMx?.name + //val vmVersion = runtimeMx?.vmVersion + //val vmVendor = runtimeMx?.vmVendor + val vmArguments = runtimeMx?.inputArguments?.joinToString(" ") + + //botInfoPanel.add(systemInfoLabel) + botInfoLabel.font = Font("monospaced", Font.PLAIN, 12) + botInfoPanel.add(botInfoLabel) + + tabs.add("システム情報", scrollPane) + + contentPane.add(tabs) + pack() + setLocationRelativeTo(null) + isVisible = true + addWindowListener(object : WindowListener { + override fun windowOpened(e: WindowEvent) { + /* unused */ + } + + override fun windowClosing(e: WindowEvent) { + try { + bot.shutdown() + } catch (ex: Exception) { + exitProcess(0) + } + } + + override fun windowClosed(e: WindowEvent) { /* unused */ + } + + override fun windowIconified(e: WindowEvent) { /* unused */ + } + + override fun windowDeiconified(e: WindowEvent) { /* unused */ + } + + override fun windowActivated(e: WindowEvent) { /* unused */ + } + + override fun windowDeactivated(e: WindowEvent) { /* unused */ + } + }) + + // ボット情報を定期的に更新する + Timer(1000) { + val uptime = Duration.ofMillis(runtimeMx!!.uptime).toMinutes() + + // Convert start time to LocalDateTime + val start = LocalDateTime.ofInstant(Instant.ofEpochMilli(runtimeMx!!.startTime), ZoneId.systemDefault()) + val vmName = runtimeMx!!.name + val vmVendor = runtimeMx!!.vmVendor + val vmVersion = runtimeMx!!.vmVersion + //val gcCount = garbageCollectors!!.sumOf { it.collectionCount } + + val cpuUsage = sunOsMx!!.processCpuLoad * 100 + val systemCpuUsage = sunOsMx!!.cpuLoad * 100 + val loadAverage = sunOsMx!!.systemLoadAverage + + val physicalMemory = sunOsMx!!.totalMemorySize / 1024 / 1024 + val freePhysicalMemory = sunOsMx!!.freeMemorySize / 1024 / 1024 + val usedPhysicalMemory = physicalMemory - freePhysicalMemory + + val swapSpace = sunOsMx!!.totalSwapSpaceSize / 1024 / 1024 + val freeSwapSpace = sunOsMx!!.freeSwapSpaceSize / 1024 / 1024 + + val heapMemory = memoryMx!!.heapMemoryUsage + val heapInit = heapMemory.init / 1024 / 1024 + val heapMax = heapMemory.max / 1024 / 1024 + val heapCommitted = heapMemory.committed / 1024 / 1024 + val heapUsed = heapMemory.used / 1024 / 1024 + + val nonHeapMemory = memoryMx!!.nonHeapMemoryUsage + val nonHeapInit = nonHeapMemory.init / 1024 / 1024 + val nonHeapMax = nonHeapMemory.max / 1024 / 1024 + val nonHeapCommitted = nonHeapMemory.committed / 1024 / 1024 + val nonHeapUsed = nonHeapMemory.used / 1024 / 1024 + + val threadCount = sunThreadMx!!.threadCount + val daemonThreadCount = sunThreadMx!!.daemonThreadCount + val peakThreadCount = sunThreadMx!!.peakThreadCount + val totalStartedThreadCount = sunThreadMx!!.totalStartedThreadCount + + val loadedClassCount = classLoadingMx!!.loadedClassCount + val totalLoadedClassCount = classLoadingMx!!.totalLoadedClassCount + val unloadedClassCount = classLoadingMx!!.unloadedClassCount + + val gcStats = garbageCollectors!!.joinToString("\n") { gc -> + val name = gc.name + val count = gc.collectionCount + val time = gc.collectionTime / 1000 + "$name: Count=$count, Time=$time sec" + } + + val botInfoText = """ + :: System
+ CPU usage: %.2f, Load average (last minute): %.5f
+ Physical memory: %s, Free: %s, Used: %s
+ Swap space: %s, Free: %s

+ :: VM Process
+ Uptime: %s minutes, Started: %s
+ CPU usage: %.2f, CPU time: %s ms, JIT compile time: %s ms

+ :: Heap
+ Current: %s, Committed: %s, Init: %s, Max: %s

+ :: Non-Heap Memory
+ Current: %d MB, Committed: %d MB, Init: %d MB, Max: %d MB

+ :: Thread Usage
+ Live: %d, Peak: %d, Daemon: %d, Total Started: %d

+ :: Class Loading
+ Current loaded: %d, Loaded (total): %d, Unloaded (total): %d

+ :: Garbage Collector
+ $gcStats + """.trimIndent() + + val sysInfoText = """ + :: System Information
+ Operating system: %s, Version: %s, Arch: %s
+ Number of processors: %d, Physical memory: %s, Virtual memory: %s

+ :: VM Information
+ VM: %s, Version: %s, Vendor: %s, JIT compiler: %s
+ Arguments: %s


+ """.trimIndent() + + val systemInfo = sysInfoText.format( + osName, osVersion, osArch, processors, + prettyBytes(sunOsMx!!.totalMemorySize), prettyBytes(sunOsMx!!.committedVirtualMemorySize), + vmName, vmVersion, vmVendor, compilationMx?.name, + vmArguments + ) + + botInfoLabel.text = "$systemInfo" + botInfoText.format( + cpuUsage, + loadAverage, + physicalMemory, + freePhysicalMemory, + usedPhysicalMemory, + swapSpace, + freeSwapSpace, + uptime, + start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), + systemCpuUsage, + sunOsMx!!.processCpuTime / 1000000, + compilationMx!!.totalCompilationTime, + heapUsed, + heapCommitted, + heapInit, + heapMax, + nonHeapUsed, + nonHeapCommitted, + nonHeapInit, + nonHeapMax, + threadCount, + peakThreadCount, + daemonThreadCount, + totalStartedThreadCount, + loadedClassCount, + totalLoadedClassCount, + unloadedClassCount + ) + "" + + }.start() + + } + + private fun prettyBytes(bytes: Long): String { + return prettyBytes(bytes, true) + } + + private fun prettyBytes(bytes: Long, si: Boolean): String { + val unit = if (si) 1000 else 1024 + if (bytes < unit) return "$bytes B" + val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() + val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1].toString() + if (si) "" else "i" + return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre) + } + +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/gui/TextAreaOutputStream.kt b/src/main/java/dev/cosgy/textToSpeak/gui/TextAreaOutputStream.kt new file mode 100644 index 0000000..7e804af --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/gui/TextAreaOutputStream.kt @@ -0,0 +1,141 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.gui + +import java.awt.EventQueue +import java.io.OutputStream +import java.io.UnsupportedEncodingException +import java.nio.charset.Charset +import java.util.* +import javax.swing.JTextArea + +/** + * @author Kosugi_kun + */ +class TextAreaOutputStream @JvmOverloads constructor(txtara: JTextArea, maxlin: Int = 1000) : OutputStream() { + private val oneByte: ByteArray + private var appender: Appender? + + init { + require(maxlin >= 1) { "TextAreaOutputStreamの最大行数は正数でなければなりません(value=$maxlin)" } + oneByte = ByteArray(1) + appender = Appender(txtara, maxlin) + } + + @Synchronized + fun clear() { + if (appender != null) { + appender!!.clear() + } + } + + @Synchronized + override fun close() { + appender = null + } + + @Synchronized + override fun flush() { + /* empty */ + } + + @Synchronized + override fun write(`val`: Int) { + oneByte[0] = `val`.toByte() + write(oneByte, 0, 1) + } + + @Synchronized + override fun write(ba: ByteArray) { + write(ba, 0, ba.size) + } + + @Synchronized + override fun write(ba: ByteArray, str: Int, len: Int) { + if (appender != null) { + appender!!.append(bytesToString(ba, str, len)) + } + } + + internal class Appender(private val textArea: JTextArea, private val maxLines: Int) : Runnable { + private val lengths: LinkedList = LinkedList() + private val values: MutableList + private var curLength = 0 + private var clear = false + private var queue = true + + init { + values = ArrayList() + } + + @Synchronized + fun append(`val`: String) { + values.add(`val`) + if (queue) { + queue = false + EventQueue.invokeLater(this) + } + } + + @Synchronized + fun clear() { + clear = true + curLength = 0 + lengths.clear() + values.clear() + if (queue) { + queue = false + EventQueue.invokeLater(this) + } + } + + @Synchronized + override fun run() { + if (clear) { + textArea.text = "" + } + values.stream() + .peek { `val`: String -> curLength += `val`.length } + .peek { `val`: String -> + if (`val`.endsWith(EOL1) || `val`.endsWith(EOL2)) { + if (lengths.size >= maxLines) { + textArea.replaceRange("", 0, lengths.removeFirst()) + } + lengths.addLast(curLength) + curLength = 0 + } + }.forEach { str: String? -> textArea.append(str) } + values.clear() + clear = false + queue = true + } + + companion object { + private const val EOL1 = "\n" + private val EOL2 = System.getProperty("line.separator", EOL1) + } + } + + companion object { + private fun bytesToString(ba: ByteArray, str: Int, len: Int): String { + return try { + String(ba, str, len, Charset.defaultCharset()) + } catch (thr: UnsupportedEncodingException) { + String(ba, str, len) + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/listeners/CommandAudit.java b/src/main/java/dev/cosgy/textToSpeak/listeners/CommandAudit.kt similarity index 54% rename from src/main/java/dev/cosgy/TextToSpeak/listeners/CommandAudit.java rename to src/main/java/dev/cosgy/textToSpeak/listeners/CommandAudit.kt index 57accdf..ee1b5a9 100644 --- a/src/main/java/dev/cosgy/TextToSpeak/listeners/CommandAudit.java +++ b/src/main/java/dev/cosgy/textToSpeak/listeners/CommandAudit.kt @@ -13,29 +13,30 @@ // See the License for the specific language governing permissions and / // limitations under the License. / ////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.listeners -package dev.cosgy.TextToSpeak.listeners; +import com.jagrosh.jdautilities.command.Command +import com.jagrosh.jdautilities.command.CommandEvent +import com.jagrosh.jdautilities.command.CommandListener +import dev.cosgy.textToSpeak.TextToSpeak +import net.dv8tion.jda.api.entities.channel.ChannelType +import org.slf4j.LoggerFactory -import com.jagrosh.jdautilities.command.Command; -import com.jagrosh.jdautilities.command.CommandEvent; -import com.jagrosh.jdautilities.command.CommandListener; -import dev.cosgy.TextToSpeak.TextToSpeak; -import net.dv8tion.jda.api.entities.channel.ChannelType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CommandAudit implements CommandListener { - @Override - public void onCommand(CommandEvent event, Command command) { +class CommandAudit : CommandListener { + override fun onCommand(event: CommandEvent, command: Command) { if (TextToSpeak.COMMAND_AUDIT_ENABLED) { - Logger logger = LoggerFactory.getLogger("CommandAudit"); - String textFormat = event.isFromType(ChannelType.PRIVATE) ? "%s%s で %s#%s (%s) がコマンド %s を実行しました" : "%s の #%s で %s#%s (%s) がコマンド %s を実行しました"; - - logger.info(String.format(textFormat, - event.isFromType(ChannelType.PRIVATE) ? "DM" : event.getGuild().getName(), - event.isFromType(ChannelType.PRIVATE) ? "" : event.getTextChannel().getName(), - event.getAuthor().getName(), event.getAuthor().getDiscriminator(), event.getAuthor().getId(), - event.getMessage().getContentDisplay())); + val logger = LoggerFactory.getLogger("CommandAudit") + val textFormat = + if (event.isFromType(ChannelType.PRIVATE)) "%s%s で %s#%s (%s) がコマンド %s を実行しました" else "%s の #%s で %s#%s (%s) がコマンド %s を実行しました" + logger.info( + String.format( + textFormat, + if (event.isFromType(ChannelType.PRIVATE)) "DM" else event.guild.name, + if (event.isFromType(ChannelType.PRIVATE)) "" else event.textChannel.name, + event.author.name, event.author.discriminator, event.author.id, + event.message.contentDisplay + ) + ) } } -} +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/listeners/MessageListener.kt b/src/main/java/dev/cosgy/textToSpeak/listeners/MessageListener.kt new file mode 100644 index 0000000..5bbb0f3 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/listeners/MessageListener.kt @@ -0,0 +1,103 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.listeners + +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist +import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import dev.cosgy.textToSpeak.Bot +import dev.cosgy.textToSpeak.audio.AudioHandler +import dev.cosgy.textToSpeak.audio.QueuedTrack +import dev.cosgy.textToSpeak.utils.ReadChannel +import net.dv8tion.jda.api.entities.channel.ChannelType +import net.dv8tion.jda.api.events.message.MessageReceivedEvent +import net.dv8tion.jda.api.events.session.ReadyEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter +import java.io.IOException + +class MessageListener(private val bot: Bot) : ListenerAdapter() { + override fun onMessageReceived(event: MessageReceivedEvent) { + event.jda + event.responseNumber + + //イベント固有の情報 + val author = event.author //メッセージを送信したユーザー + val message = event.message //受信したメッセージ。 + event.channel //メッセージが送信されたMessageChannel + var msg = message.contentDisplay + //人間が読める形式のメッセージが返されます。 クライアントに表示されるものと同様。 + val isBot = author.isBot + //if(Arrays.asList(mentionedUsers).contains()) + //メッセージを送信したユーザーがBOTであるかどうかを判断。 + if (event.isFromType(ChannelType.TEXT)) { + if (isBot) return + val guild = event.guild + val textChannel = event.guildChannel.asTextChannel() + var settingText = bot.settingsManager.getSettings(event.guild).getTextChannel(event.guild) + if (!guild.audioManager.isConnected) { + return + } + val prefix = if (bot.config.prefix == "@mention") "@" + event.jda.selfUser.name + " " else bot.config.prefix + if (prefix?.let { msg.startsWith(it) } == true) { + return + } + if (textChannel !== settingText) { + if (settingText == null) { + settingText = event.guild.getTextChannelById(ReadChannel.getChannel(event.guild.idLong)!!) + } + } + + // URLを置き換え + msg = msg.replace("(http://|https://)[\\w.\\-/:#?=&;%~+]+".toRegex(), "ゆーあーるえる") + if (textChannel === settingText) { + if (bot.settingsManager.getSettings(guild).isReadName()) { + msg = author.name + " " + msg + } + val vc = bot.voiceCreation + val file: String? = try { + vc.createVoice(guild, author, msg) + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + bot.playerManager.loadItemOrdered(event.guild, file, ResultHandler(event)) + + //textChannel.sendMessage(author.getName() + "が、「"+ msg +"」と送信しました。").queue(); + } + } + } + + override fun onReady(e: ReadyEvent) { + bot.readyJDA() + } + + private class ResultHandler(private val event: MessageReceivedEvent) : AudioLoadResultHandler { + private fun loadSingle(track: AudioTrack) { + val handler = event.guild.audioManager.sendingHandler as AudioHandler? + handler!!.addTrack(QueuedTrack(track, event.author)) + } + + override fun trackLoaded(track: AudioTrack) { + loadSingle(track) + } + + override fun playlistLoaded(playlist: AudioPlaylist) {} + override fun noMatches() {} + override fun loadFailed(throwable: FriendlyException) {} + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/queue/FairQueue.kt b/src/main/java/dev/cosgy/textToSpeak/queue/FairQueue.kt new file mode 100644 index 0000000..d0cec88 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/queue/FairQueue.kt @@ -0,0 +1,79 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.queue + +class FairQueue { + private val list: MutableList = ArrayList() + private val set: MutableSet = HashSet() + fun add(item: T): Int { + var lastIndex: Int = list.size - 1 + while (lastIndex > -1) { + if (list[lastIndex]!!.identifier == item!!.identifier) break + lastIndex-- + } + lastIndex++ + set.clear() + while (lastIndex < list.size) { + if (set.contains(list[lastIndex]!!.identifier)) break + set.add(list[lastIndex]!!.identifier) + lastIndex++ + } + list.add(lastIndex, item) + return lastIndex + } + + fun addAt(index: Int, item: T) { + if (index >= list.size) list.add(item) else list.add(index, item) + } + + fun size(): Int { + return list.size + } + + fun pull(): T { + return list.removeAt(0) + } + + val isEmpty: Boolean + get() = list.isEmpty() + + fun getList(): List { + return list + } + + operator fun get(index: Int): T { + return list[index] + } + + fun remove(index: Int): T { + return list.removeAt(index) + } + + fun removeAll(identifier: Long): Int { + var count = 0 + for (i in list.indices.reversed()) { + if (list[i]!!.identifier == identifier) { + list.removeAt(i) + count++ + } + } + return count + } + + fun clear() { + list.clear() + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/queue/Queueable.java b/src/main/java/dev/cosgy/textToSpeak/queue/Queueable.kt similarity index 93% rename from src/main/java/dev/cosgy/TextToSpeak/queue/Queueable.java rename to src/main/java/dev/cosgy/textToSpeak/queue/Queueable.kt index 2b28749..9d286a3 100644 --- a/src/main/java/dev/cosgy/TextToSpeak/queue/Queueable.java +++ b/src/main/java/dev/cosgy/textToSpeak/queue/Queueable.kt @@ -13,9 +13,8 @@ // See the License for the specific language governing permissions and / // limitations under the License. / ////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.queue -package dev.cosgy.TextToSpeak.queue; - -public interface Queueable { - long getIdentifier(); -} +interface Queueable { + val identifier: Long +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/settings/Settings.kt b/src/main/java/dev/cosgy/textToSpeak/settings/Settings.kt new file mode 100644 index 0000000..6ba0e09 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/settings/Settings.kt @@ -0,0 +1,96 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.settings + +import com.jagrosh.jdautilities.command.GuildSettingsProvider +import net.dv8tion.jda.api.entities.Guild +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel + +class Settings : GuildSettingsProvider { + private val manager: SettingsManager + var textId: Long = 0 + var prefix: String? + var volume: Int + private var readName: Boolean + private var joinAndLeaveRead: Boolean + + constructor( + manager: SettingsManager, + textId: String?, + prefix: String?, + volume: Int, + readName: Boolean, + joinAndLeaveRead: Boolean + ) { + this.manager = manager + try { + this.textId = textId?.toLong() ?: 0 + } catch (e: NumberFormatException) { + this.textId = 0 + } + this.prefix = prefix + this.volume = volume + this.readName = readName + this.joinAndLeaveRead = joinAndLeaveRead + } + + constructor( + manager: SettingsManager, + textId: Long, + prefix: String?, + volume: Int, + readName: Boolean, + joinAndLeaveRead: Boolean + ) { + this.manager = manager + this.textId = textId + this.prefix = prefix + this.volume = volume + this.readName = readName + this.joinAndLeaveRead = joinAndLeaveRead + } + + fun getTextChannel(guild: Guild?): TextChannel? { + return guild?.getTextChannelById(textId) + } + + fun setTextChannel(tc: TextChannel?) { + textId = tc?.idLong ?: 0 + manager.writeSettings() + } + + override fun getPrefixes(): Collection { + return if (prefix == null) emptySet() else setOf(prefix) + } + + fun isReadName(): Boolean { + return readName + } + + fun setReadName(readName: Boolean) { + this.readName = readName + manager.writeSettings() + } + + fun isJoinAndLeaveRead(): Boolean { + return joinAndLeaveRead + } + + fun setJoinAndLeaveRead(joinAndLeaveRead: Boolean) { + this.joinAndLeaveRead = joinAndLeaveRead + manager.writeSettings() + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/settings/SettingsManager.kt b/src/main/java/dev/cosgy/textToSpeak/settings/SettingsManager.kt new file mode 100644 index 0000000..3670375 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/settings/SettingsManager.kt @@ -0,0 +1,90 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.settings + +import com.jagrosh.jdautilities.command.GuildSettingsManager +import dev.cosgy.textToSpeak.utils.OtherUtil +import net.dv8tion.jda.api.entities.Guild +import org.json.JSONException +import org.json.JSONObject +import org.slf4j.LoggerFactory +import java.io.IOException +import java.nio.file.Files +import java.util.function.Consumer + +class SettingsManager : GuildSettingsManager { + private val settings: HashMap = HashMap() + + init { + try { + val loadedSettings = JSONObject(String(Files.readAllBytes(OtherUtil.getPath("serversettings.json")))) + loadedSettings.keySet().forEach { id -> + val value = loadedSettings.getJSONObject(id) + settings[id.toLong()] = Settings( + this, + if (value.has("text_channel_id")) value.getString("text_channel_id") else null, + if (value.has("prefix")) value.getString("prefix") else null, + if (value.has("volume")) value.getInt("volume") else 50, + value.has("read_name") && value.getBoolean("read_name"), + value.has("join_and_leave_read") && value.getBoolean("join_and_leave_read") + ) + } + } catch (e: IOException) { + LoggerFactory.getLogger("Settings").warn("サーバー設定を読み込めませんでした(まだ設定がない場合は正常です): $e") + } catch (e: JSONException) { + LoggerFactory.getLogger("Settings").warn("サーバー設定を読み込めませんでした(まだ設定がない場合は正常です): $e") + } + } + + override fun getSettings(guild: Guild): Settings { + return getSettings(guild.idLong) + } + + fun getSettings(guildId: Long): Settings { + return settings.computeIfAbsent(guildId) { _: Long? -> createDefaultSettings() } + } + + /** + * デフォルト設定のデータを作って返す。 + * + * @return 作成されたデフォルト設定 + */ + private fun createDefaultSettings(): Settings { + return Settings(this, 0, null, 50, readName = false, joinAndLeaveRead = false) + } + + /** + * 設定をファイルに書き込む + */ + fun writeSettings() { + val obj = JSONObject() + settings.keys.forEach(Consumer { key: Long -> + val o = JSONObject() + val s = settings[key] + if (s!!.textId != 0L) o.put("text_channel_id", s.textId.toString()) + if (s.prefix != null) o.put("prefix", s.prefix) + if (s.volume != 50) o.put("volume", s.volume) + if (s.isReadName()) o.put("read_name", s.isReadName()) + if (s.isJoinAndLeaveRead()) o.put("join_and_leave_read", s.isJoinAndLeaveRead()) + obj.put(key.toString(), o) + }) + try { + Files.write(OtherUtil.getPath("serversettings.json"), obj.toString(4).toByteArray()) + } catch (ex: IOException) { + LoggerFactory.getLogger("Settings").warn("ファイルへの書き込みに失敗しました: $ex") + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/settings/UserSettings.kt b/src/main/java/dev/cosgy/textToSpeak/settings/UserSettings.kt new file mode 100644 index 0000000..9626744 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/settings/UserSettings.kt @@ -0,0 +1,61 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.settings + +class UserSettings( + private val manager: UserSettingsManager, + private val userId: Long, + private var voice: String, + private var speed: Float, + private var intonation: Float, + private var voiceQualityA: Float, + private var voiceQualityFm: Float +) { + var voiceSetting: String + get() = voice + set(value) { + voice = value + manager.saveSetting(userId) + } + + var speedSetting: Float + get() = speed + set(value) { + speed = value + manager.saveSetting(userId) + } + + var intonationSetting: Float + get() = intonation + set(value) { + intonation = value + manager.saveSetting(userId) + } + + var voiceQualityASetting: Float + get() = voiceQualityA + set(value) { + voiceQualityA = value + manager.saveSetting(userId) + } + + var voiceQualityFmSetting: Float + get() = voiceQualityFm + set(value) { + voiceQualityFm = value + manager.saveSetting(userId) + } +} diff --git a/src/main/java/dev/cosgy/textToSpeak/settings/UserSettingsManager.kt b/src/main/java/dev/cosgy/textToSpeak/settings/UserSettingsManager.kt new file mode 100644 index 0000000..12dd607 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/settings/UserSettingsManager.kt @@ -0,0 +1,102 @@ +package dev.cosgy.textToSpeak.settings + +import dev.cosgy.textToSpeak.utils.OtherUtil +import org.apache.commons.io.FileUtils +import org.slf4j.LoggerFactory +import java.io.IOException +import java.nio.charset.StandardCharsets +import java.sql.Connection +import java.sql.DriverManager +import java.sql.SQLException + +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +class UserSettingsManager { + private val settings: HashMap = HashMap() + private val logger = LoggerFactory.getLogger(this.javaClass) + private var connection: Connection? = null + + init { + val path = OtherUtil.getPath("UserData.sqlite") + if (!path.toFile().exists()) { + val original = OtherUtil.loadResource(this, "UserData.sqlite") + try { + FileUtils.writeStringToFile(path.toFile(), original, StandardCharsets.UTF_8) + logger.info("データベースファイルが存在しなかったためファイルを作成しました。") + } catch (e: IOException) { + logger.error("データベースファイルを作成できませんでした。", e) + } + } + try { + connection = DriverManager.getConnection("jdbc:sqlite:UserData.sqlite") + val statement = connection!!.createStatement() + val sql = + "create table if not exists settings ( id integer not null constraint settings_pk primary key, voice TEXT, speed real, intonation real, voiceQualityA real, voiceQualityFm real)" + statement.execute(sql) + val rs = statement.executeQuery("select * from settings") + while (rs.next()) { + settings[rs.getLong(1)] = UserSettings( + this, + rs.getLong(1), + rs.getString(2), + rs.getFloat(3), + rs.getFloat(4), + rs.getFloat(5), + rs.getFloat(6) + ) + } + } catch (throwables: SQLException) { + logger.error("データベースに接続できませんでした。", throwables) + } + } + + fun getSettings(userId: Long): UserSettings { + return settings.computeIfAbsent(userId) { _: Long -> createDefaultSettings(userId) } + } + + private fun createDefaultSettings(userId: Long): UserSettings { + return UserSettings(this, userId, "mei_normal", 1.0f, 1.0f, 0.5f, 2.0f) + } + + fun saveSetting(userId: Long) { + val sql = + "REPLACE INTO settings (id, voice, speed, intonation, voiceQualityA, voiceQualityFm) VALUES (?,?,?,?,?,?)" + val settings = settings[userId] + try { + connection!!.prepareStatement(sql).use { ps -> + ps.setLong(1, userId) + ps.setString(2, settings!!.voiceSetting) + ps.setFloat(3, settings.speedSetting) + ps.setFloat(4, settings.intonationSetting) + ps.setFloat(5, settings.voiceQualityASetting) + ps.setFloat(6, settings.voiceQualityFmSetting) + logger.debug(ps.toString()) + ps.executeUpdate() + } + } catch (throwables: SQLException) { + logger.error("設定を保存できませんでした。", throwables) + } + } + + fun closeConnection() { + try { + connection!!.close() + logger.info("データベース接続を終了しました。") + } catch (e: SQLException) { + logger.error("データベース接続を終了できませんでした。", e) + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/utils/FormatUtil.java b/src/main/java/dev/cosgy/textToSpeak/utils/FormatUtil.kt similarity index 69% rename from src/main/java/dev/cosgy/TextToSpeak/utils/FormatUtil.java rename to src/main/java/dev/cosgy/textToSpeak/utils/FormatUtil.kt index adbe2ea..34c75cf 100644 --- a/src/main/java/dev/cosgy/TextToSpeak/utils/FormatUtil.java +++ b/src/main/java/dev/cosgy/textToSpeak/utils/FormatUtil.kt @@ -13,20 +13,19 @@ // See the License for the specific language governing permissions and / // limitations under the License. / ////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.utils -package dev.cosgy.TextToSpeak.utils; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; - -import java.util.List; - -public class FormatUtil { - public static String listOfTChannels(List list, String query) { - StringBuilder out = new StringBuilder(" 複数のテキストチャンネルで\"" + query + "\"が一致しました。:"); - for (int i = 0; i < 6 && i < list.size(); i++) - out.append("\n - ").append(list.get(i).getName()).append(" (<#").append(list.get(i).getId()).append(">)"); - if (list.size() > 6) - out.append("\n**と ").append(list.size() - 6).append(" など...**"); - return out.toString(); +object FormatUtil { + fun listOfTChannels(list: List, query: String): String { + val out = StringBuilder(" 複数のテキストチャンネルで\"$query\"が一致しました。:") + var i = 0 + while (i < 6 && i < list.size) { + out.append("\n - ").append(list[i].name).append(" (<#").append(list[i].id).append(">)") + i++ + } + if (list.size > 6) out.append("\n**と ").append(list.size - 6).append(" など...**") + return out.toString() } -} +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/textToSpeak/utils/OtherUtil.kt b/src/main/java/dev/cosgy/textToSpeak/utils/OtherUtil.kt new file mode 100644 index 0000000..102c075 --- /dev/null +++ b/src/main/java/dev/cosgy/textToSpeak/utils/OtherUtil.kt @@ -0,0 +1,254 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 Cosgy Dev / +// / +// Licensed under the Apache License, Version 2.0 (the "License"); / +// you may not use this file except in compliance with the License. / +// You may obtain a copy of the License at / +// / +// http://www.apache.org/licenses/LICENSE-2.0 / +// / +// Unless required by applicable law or agreed to in writing, software / +// distributed under the License is distributed on an "AS IS" BASIS, / +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / +// See the License for the specific language governing permissions and / +// limitations under the License. / +////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.utils + +import dev.cosgy.textToSpeak.TextToSpeak +import dev.cosgy.textToSpeak.entities.Prompt +import net.dv8tion.jda.api.OnlineStatus +import net.dv8tion.jda.api.entities.Activity +import okhttp3.OkHttpClient +import okhttp3.Request +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.slf4j.LoggerFactory +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.net.URISyntaxException +import java.nio.file.Path +import java.nio.file.Paths +import java.util.* + +object OtherUtil { + const val NEW_VERSION_AVAILABLE = ("利用可能な新しいバージョンがあります!\n" + + "現在のバージョン: %s\n" + + "最新のバージョン: %s\n\n" + + " https://github.com/Cosgy-Dev/TextToSpeakBot/releases/latest から最新バージョンをダウンロードして下さい。") + const val NEW_BETA_VERSION_AVAILABLE = ("利用可能な新しいベータバージョンがあります!\n" + + "現在のバージョン: %s\n" + + "最新のバージョン: %s\n\n" + + " https://github.com/Cosgy-Dev/TextToSpeakBot/releases/tag/%s から最新バージョンをダウンロードして下さい。") + private const val WINDOWS_INVALID_PATH = "c:\\windows\\system32\\" + fun getPath(path: String): Path { + // special logic to prevent trying to access system32 + var returnPath = path + if (returnPath.lowercase(Locale.getDefault()).startsWith(WINDOWS_INVALID_PATH)) { + val filename = returnPath.substring(WINDOWS_INVALID_PATH.length) + try { + returnPath = + File(TextToSpeak::class.java.protectionDomain.codeSource.location.toURI()).parentFile.path + File.separator + filename + } catch (ex: URISyntaxException) { + ex.printStackTrace() + } + } + return Paths.get(returnPath) + } + + /** + * jarからリソースを文字列としてロードします + * + * @param clazz クラスベースオブジェクト + * @param name リソースの名前 + * @return リソースの内容を含む文字列 + */ + fun loadResource(clazz: Any, name: String?): String? { + return try { + name?.let { clazz.javaClass.getResourceAsStream(it)?.let { it -> readString(it) } } + } catch (ex: Exception) { + null + } + } + + @Throws(IOException::class) + fun readString(inputStream: InputStream): String { + val into = ByteArrayOutputStream() + val buf = ByteArray(32768) + var n: Int + while (0 < inputStream.read(buf).also { n = it }) { + into.write(buf, 0, n) + } + into.close() + return into.toString("utf-8") + } + + /** + * 文字列からアクティビティを解析 + * + * @param game the game, including the action such as 'playing' or 'watching' + * @return the parsed activity + */ + fun parseGame(game: String?): Activity? { + if (game == null || game.trim { it <= ' ' }.isEmpty() || game.trim { it <= ' ' } + .equals("default", ignoreCase = true)) return null + val lower = game.lowercase(Locale.getDefault()) + if (lower.startsWith("playing")) return Activity.playing(makeNonEmpty(game.substring(7).trim { it <= ' ' })) + if (lower.startsWith("listening to")) return Activity.listening( + makeNonEmpty( + game.substring(12).trim { it <= ' ' }) + ) + if (lower.startsWith("listening")) return Activity.listening(makeNonEmpty(game.substring(9).trim { it <= ' ' })) + if (lower.startsWith("watching")) return Activity.watching(makeNonEmpty(game.substring(8).trim { it <= ' ' })) + if (lower.startsWith("streaming")) { + val parts = game.substring(9).trim { it <= ' ' }.split("\\s+".toRegex(), limit = 2).toTypedArray() + if (parts.size == 2) { + return Activity.streaming(makeNonEmpty(parts[1]), "https://twitch.tv/" + parts[0]) + } + } + return Activity.playing(game) + } + + private fun makeNonEmpty(str: String?): String { + return if (str.isNullOrEmpty()) "\u200B" else str + } + + fun parseStatus(status: String?): OnlineStatus { + if (status == null || status.trim { it <= ' ' }.isEmpty()) return OnlineStatus.ONLINE + val st = OnlineStatus.fromKey(status) + return st ?: OnlineStatus.ONLINE + } + + fun checkVersion(prompt: Prompt): String { + // Get current version number + val version = currentVersion + + if (!isBetaVersion(version)) { + // Check for new version + val latestVersion = latestVersion + if (latestVersion != null && latestVersion != version && TextToSpeak.CHECK_UPDATE) { + prompt.alert( + Prompt.Level.WARNING, + "Version", + String.format(NEW_VERSION_AVAILABLE, version, latestVersion) + ) + } + } else { + val latestBeta = latestBetaVersion + if (latestBeta != null && compareVersions(version, latestBeta) == 0) { + prompt.alert(Prompt.Level.INFO, "Beta Version", "最新のベータバージョンを使用中です。") + } else { + prompt.alert( + Prompt.Level.WARNING, + "Beta Version", + String.format(NEW_BETA_VERSION_AVAILABLE, version, latestBetaVersion, latestBetaVersion) + ) + } + } + + // Return the current version + return version + } + + val currentVersion: String + get() = if (TextToSpeak::class.java.getPackage() != null && TextToSpeak::class.java.getPackage().implementationVersion != null) TextToSpeak::class.java.getPackage().implementationVersion else "不明" + val latestVersion: String? + get() { + try { + val client = OkHttpClient() + + val request = Request.Builder() + .url("https://api.github.com/repos/Cosgy-Dev/TextToSpeakBot/releases/latest") + .build() + + val response = client.newCall(request).execute() + val body = response.body?.string() + + if (body != null) { + try { + val obj = JSONObject(body) + return obj.getString("tag_name") + } catch (e: JSONException) { + LoggerFactory.getLogger("Settings").warn("タグ名を解析できませんでした: $e") + } + } else return null + } catch (ex: IOException) { + return null + } catch (ex: JSONException) { + return null + } catch (ex: NullPointerException) { + return null + } + return null + } + + private val latestBetaVersion: String? + get() { + try { + val client = OkHttpClient() + + val request = Request.Builder() + .url("https://api.github.com/repos/Cosgy-Dev/TextToSpeakBot/releases") + .build() + + val response = client.newCall(request).execute() + val body = response.body?.string() + val releases = JSONArray(body) + + var latestBetaTag: String? = null + for (i in 0 until releases.length()) { + val release = releases.getJSONObject(i) + val isPrerelease = release.getBoolean("prerelease") + + if (isPrerelease) { + val tagName = release.getString("tag_name") + if (latestBetaTag == null || compareVersions(tagName, latestBetaTag) > 0) { + latestBetaTag = tagName + } + } + } + + return latestBetaTag ?: "ベータリリースなし" + } catch (e: Exception) { + println("Failed to retrieve releases: ${e.message}") + } + return null + } + + private fun isBetaVersion(version: String): Boolean { + val versionParts = version.split("-") + return versionParts.lastOrNull()?.startsWith("beta") ?: false + } + + + private fun compareVersions(version1: String, version2: String): Int { + val parts1 = version1.split("[.-]".toRegex()).toTypedArray() + val parts2 = version2.split("[.-]".toRegex()).toTypedArray() + val length = maxOf(parts1.size, parts2.size) + + for (i in 0 until length) { + val part1 = if (i < parts1.size) parseVersionPart(parts1[i]) else 0 + val part2 = if (i < parts2.size) parseVersionPart(parts2[i]) else 0 + + if (part1 < part2) { + return -1 + } else if (part1 > part2) { + return 1 + } + } + + return 0 + } + + private fun parseVersionPart(part: String): Int { + return if (part.matches("\\d+".toRegex())) { + part.toInt() + } else { + 0 + } + } + +} \ No newline at end of file diff --git a/src/main/java/dev/cosgy/TextToSpeak/utils/ReadChannel.java b/src/main/java/dev/cosgy/textToSpeak/utils/ReadChannel.kt similarity index 80% rename from src/main/java/dev/cosgy/TextToSpeak/utils/ReadChannel.java rename to src/main/java/dev/cosgy/textToSpeak/utils/ReadChannel.kt index 1078f26..d5b06bc 100644 --- a/src/main/java/dev/cosgy/TextToSpeak/utils/ReadChannel.java +++ b/src/main/java/dev/cosgy/textToSpeak/utils/ReadChannel.kt @@ -13,19 +13,15 @@ // See the License for the specific language governing permissions and / // limitations under the License. / ////////////////////////////////////////////////////////////////////////////////////////// +package dev.cosgy.textToSpeak.utils -package dev.cosgy.TextToSpeak.utils; - -import java.util.HashMap; - -public class ReadChannel { - private static HashMap chat = new HashMap<>(); - - public static void setChannel(Long guild, Long textChannel) { - chat.put(guild, textChannel); +object ReadChannel { + private val chat = HashMap() + fun setChannel(guild: Long, textChannel: Long) { + chat[guild] = textChannel } - public static Long getChannel(Long guild) { - return chat.get(guild); + fun getChannel(guild: Long): Long? { + return chat[guild] } -} +} \ No newline at end of file diff --git a/src/main/resources/lang/yomiage.properties b/src/main/resources/lang/yomiage.properties deleted file mode 100644 index 24718b0..0000000 --- a/src/main/resources/lang/yomiage.properties +++ /dev/null @@ -1,18 +0,0 @@ -########################################################################################## -# Copyright 2023 Cosgy Dev # -# # -# Licensed under the Apache License, Version 2.0 (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at # -# # -# http://www.apache.org/licenses/LICENSE-2.0 # -# # -# Unless required by applicable law or agreed to in writing, software # -# distributed under the License is distributed on an "AS IS" BASIS, # -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# See the License for the specific language governing permissions and # -# limitations under the License. # -########################################################################################## -appName=\u8AAD\u307F\u4E0A\u3052\u30DC\u30C3\u30C8 by Cosgy Dev -categoryAdmin="\u7BA1\u7406" -categorySetting="\u8A2D\u5B9A" \ No newline at end of file diff --git a/src/main/resources/lang/yomiage_en.properties b/src/main/resources/lang/yomiage_en.properties deleted file mode 100644 index 65f6513..0000000 --- a/src/main/resources/lang/yomiage_en.properties +++ /dev/null @@ -1,18 +0,0 @@ -########################################################################################## -# Copyright 2023 Cosgy Dev # -# # -# Licensed under the Apache License, Version 2.0 (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at # -# # -# http://www.apache.org/licenses/LICENSE-2.0 # -# # -# Unless required by applicable law or agreed to in writing, software # -# distributed under the License is distributed on an "AS IS" BASIS, # -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# See the License for the specific language governing permissions and # -# limitations under the License. # -########################################################################################## -appName=Yomiage Bot by Cosgy Dev -categoryAdmin="Admin" -categorySetting="Setting" \ No newline at end of file diff --git a/src/main/resources/lang/yomiage_ja.properties b/src/main/resources/lang/yomiage_ja.properties deleted file mode 100644 index 24718b0..0000000 --- a/src/main/resources/lang/yomiage_ja.properties +++ /dev/null @@ -1,18 +0,0 @@ -########################################################################################## -# Copyright 2023 Cosgy Dev # -# # -# Licensed under the Apache License, Version 2.0 (the "License"); # -# you may not use this file except in compliance with the License. # -# You may obtain a copy of the License at # -# # -# http://www.apache.org/licenses/LICENSE-2.0 # -# # -# Unless required by applicable law or agreed to in writing, software # -# distributed under the License is distributed on an "AS IS" BASIS, # -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# See the License for the specific language governing permissions and # -# limitations under the License. # -########################################################################################## -appName=\u8AAD\u307F\u4E0A\u3052\u30DC\u30C3\u30C8 by Cosgy Dev -categoryAdmin="\u7BA1\u7406" -categorySetting="\u8A2D\u5B9A" \ No newline at end of file diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 4ef7ffc..cfc715d 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -1,8 +1,8 @@ /// START OF YOMIAGEBOT CONFIG /// //______________________________________________________________ -// =================== -// Yomiage Botの設定 -// =================== +//=================== +// Yomiage Bot\u00E3\u0081\u00AE\u00E8\u00A8\u00AD\u00E5\u00AE\u009A +//=================== // // //で始まる行は無視されます。 // トークンと所有者を設定しなければなりません。 @@ -76,4 +76,8 @@ maxmessagecount = 0 // Windows環境でのホストは、推奨していません。 winjtalkdir = "" +// ファイルを保存する際に文字コードを強制的にUTF-8にします。 +// この設定はWindowsのみで有効です。 +forceutf-8 = false + /// END OF YOMIAGEBOT CONFIG /// \ No newline at end of file