├── .circleci └── config.yml ├── .github ├── CODE_OF_CONDUCT.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── ---.md │ ├── bug-report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── code_quality.yml ├── .gitignore ├── .space.kts ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── img └── portal-msg-intent.png ├── pom.xml ├── qodana.yaml └── src └── main ├── java └── dev │ └── cosgy │ └── textToSpeak │ ├── Bot.kt │ ├── BotConfig.kt │ ├── Listener.kt │ ├── TextToSpeak.kt │ ├── audio │ ├── AloneInVoiceHandler.kt │ ├── AudioHandler.kt │ ├── Dictionary.kt │ ├── EnglishToKatakana.kt │ ├── PlayerManager.kt │ ├── QueuedTrack.kt │ └── VoiceCreation.kt │ ├── commands │ ├── AdminCommand.kt │ ├── OwnerCommand.kt │ ├── admin │ │ ├── GuildSettings.kt │ │ ├── JLReadCmd.kt │ │ ├── NicReadCmd.kt │ │ ├── SetReadNameCmd.kt │ │ └── SettcCmd.kt │ ├── dictionary │ │ ├── AddWordCmd.kt │ │ ├── DlWordCmd.kt │ │ └── WordListCmd.kt │ ├── general │ │ ├── AboutCommand.kt │ │ ├── ByeCmd.kt │ │ ├── HelpCmd.kt │ │ ├── JoinCmd.kt │ │ ├── SetIntonationCmd.kt │ │ ├── SetSpeedCmd.kt │ │ ├── SetVoiceCmd.kt │ │ ├── SetVoiceQualityA.kt │ │ ├── SetVoiceQualityFm.kt │ │ ├── SettingsCmd.kt │ │ └── TranslateCmd.kt │ └── owner │ │ └── ShutdownCmd.kt │ ├── entities │ └── Prompt.kt │ ├── gui │ ├── ConsolePanel.kt │ ├── GUI.kt │ └── TextAreaOutputStream.kt │ ├── listeners │ ├── CommandAudit.kt │ └── MessageListener.kt │ ├── queue │ ├── FairQueue.kt │ └── Queueable.kt │ ├── settings │ ├── Settings.kt │ ├── SettingsManager.kt │ ├── UserSettings.kt │ └── UserSettingsManager.kt │ └── utils │ ├── FormatUtil.kt │ ├── OtherUtil.kt │ └── ReadChannel.kt └── resources ├── UserData.sqlite ├── bep-eng.dic ├── log4j.properties ├── logback.xml └── reference.conf /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build: 5 | docker: 6 | # specify the version you desire here 7 | - image: cimg/openjdk:20.0.1 8 | 9 | working_directory: ~/repo 10 | 11 | environment: 12 | # Customize the JVM maximum heap limit 13 | MAVEN_OPTS: -Xmx3200m 14 | 15 | steps: 16 | - checkout 17 | - run: java --version 18 | 19 | # Download and cache dependencies 20 | - restore_cache: 21 | keys: 22 | - v1-dependencies-{{ checksum "pom.xml" }} 23 | # fallback to using the latest cache if no exact match is found 24 | - v1-dependencies- 25 | 26 | - run: mvn dependency:go-offline 27 | 28 | - save_cache: 29 | paths: 30 | - ~/.m2 31 | key: v1-dependencies-{{ checksum "pom.xml" }} 32 | 33 | # run tests! 34 | - run: mvn integration-test 35 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # コントリビューター行動規範 2 | 3 | ## 私たちの約束 4 | 5 | 私たちはオープンかつ友好的なコミュニティーを育成するために、 コントリビューターやメンテナーとして、年齢、体型、障碍、民族性、 6 | 性自認および性別表現、経験レベル、国籍、個人の容姿、人種、信仰、 7 | 性的同一性および指向に関わりなく、私たちのプロジェクトやコミュニティー への参加を誰にとっても嫌がらせのない体験にすることを誓います。 8 | 9 | ## 私たちの標準 10 | 11 | 前向きな環境を作り上げることに貢献する振る舞いの例: 12 | 13 | * 友好的かつ男女包括用語の使用 14 | * 異なる観点や経験の尊重 15 | * 建設的批判の率直な受容 16 | * コミュニティーにとっての最善への集中 17 | * 他のコミュニティーメンバーへの共感 18 | 19 | 参加者による容認できない行動の例: 20 | 21 | * 性的な意味を含む言葉や画像、相手の意思に反した性的関心や接近 22 | * あおり、侮辱的または軽蔑的なコメント、個人攻撃や政治攻撃 23 | * 公的または私的な嫌がらせ 24 | * 住所、メールアドレスなど、他者のプライベート情報の明示的な許可なき公開 25 | * 職場において合理的に不適切であると考えられる他の行為 26 | 27 | ## 私たちの責任 28 | 29 | プロジェクトのメンテナーは、許容できる行動の基準を明確にすることに 責任があります。また、何かしらの許容できない行動に対応する、 30 | 適切かつ公平な是正処置をとることが期待されています。 31 | 32 | プロジェクトのメンテナーは、この行動規範に沿っていない、 コメント、コミット、コード、wiki編集、issue、その他の貢献を 33 | 削除、編集、拒否する権利と義務を有します。 34 | また、他の不適切、脅迫的、攻撃的、嫌がらせと考えられる行動を取った 35 | コントリビューターを一時的もしくは恒久的に追放する権利と義務を有します。 36 | 37 | ## 適用範囲 38 | 39 | この行動規範は、個人がプロジェクトやそのコミュニティーを代表するとき、 プロジェクト内と公共空間の両方において適用されます。プロジェクトや 40 | コミュニティーを代表する例として、プロジェクトの公式メールアドレスの 41 | 使用、ソーシャルメディアの公式アカウント経由の投稿、指名された代表 としてのオンラインやオフラインのイベントにおける行動があります。 42 | プロジェクトを代表することは、プロジェクトのメンテナーにより、 43 | さらに定義され明確化される可能性があります。 44 | 45 | ## 執行 46 | 47 | 暴言、嫌がらせ、またはそれ以外の受け入れられない行動は、 info@cosgy.dev に連絡して、 プロジェクトチームに報告される可能性があります。すべての苦情は、 48 | レビュー、調査され、必要かつ適切と判断された対応がとられます。 49 | プロジェクトチームは、事象の報告者に関する守秘義務があります。 具体的な執行に関する詳細が別途設定されているかもしれません。 50 | 51 | この行動規範に誠意を持って従うまたは執行することができない プロジェクトのメンテナーは、プロジェクトをリードしている他のメンバー 52 | の判断により、一時的または恒久的な影響を受けることがあります。 53 | 54 | ## 帰属 55 | 56 | この行動規範は https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 57 | にある [Contributor Covenant][homepage] 58 | バージョン 59 | 1.4 に適合しています。 60 | 61 | [homepage]: https://www.contributor-covenant.org 62 | 63 | この行動規範に関する一般的な質問への回答については、https://www.contributor-covenant.org/faqを参照してください。 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Cosgy-Dev, Kosugi_kun] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: その他 3 | about: その他のIssueを立てたい場合はこちら 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: バグレポート 3 | about: バグや予期しない動作を報告する 4 | title: "[バグレポート] タイトルを入力" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **バグを説明する** 11 | どのようなバグであるかの明確かつ簡潔な説明。 12 | 13 | **再現する方法** 14 | 問題の再現手順: 15 | 16 | 1. '...' を実行 17 | 2. '....'コマンドを実行する 18 | 3. その他の方法 '....' 19 | 20 | **予想される動作** 21 | どのような事がが起こるが予想されていたのかについての明確で簡潔な説明。 22 | 23 | **スクリーンショット** 24 | 該当する場合は、問題を説明するためにスクリーンショットを追加してください。 25 | 26 | **バージョン情報(以下の情報を記入してください):** 27 | 28 | - オペレーティングシステム(OS): [例 Windows 64bit] 29 | - ボットバージョン: [例 0.1.0] 30 | 31 | **追加のコンテキスト** 32 | この問題に関する他の文脈をここに追加してください。 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 機能リクエスト 3 | about: このプロジェクトにアイデアを提案 4 | title: "[機能要求]タイトルを入力" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **機能リクエストは問題に関連していますか。 記述してください。** 11 | 問題が何であるかの明確で簡潔な説明。 12 | 13 | **この機能を使用するための基本的な流れ/手順を説明する** 14 | この機能の使い方: 15 | 16 | 1. 何かを設定 '...' 17 | 2. コマンドを実行する '...' 18 | 3. その他の手順や関連情報 '...' 19 | 4. ... 20 | 21 | **追加のコンテキスト** 22 | 機能要求に関するその他のコンテキストやスクリーンショットをここに追加してください。 23 | 24 | **あなたはこの考えがまだここで説明されていないことを確認しましたか?** 25 | https://github.com/Cosgy-Dev/JMusicBot-JP/projects/1 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### このプルリクエストは... 2 | 3 | - [ ] バグを修正 4 | - [ ] 新機能を追加 5 | - [ ] 既存の機能を改善 6 | - [ ] コードの品質やパフォーマンスの向上 7 | 8 | ### 説明 9 | 10 | 変更内容などを箇条書きでお願いします。 ここで書かれた変更内容はリリースノートを作成する際に参考にします。 11 | 12 | ### 目的 13 | 14 | ここに問題の説明または活用事例を書いてください。 15 | 16 | ### 関連する問題 17 | 18 | ここには関連する問題を#[issuesの番目] で表示して下さい。 19 | -------------------------------------------------------------------------------- /.github/workflows/code_quality.yml: -------------------------------------------------------------------------------- 1 | name: Qodana 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: 7 | - '*' 8 | 9 | jobs: 10 | qodana: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - name: 'Qodana Scan' 17 | uses: JetBrains/qodana-action@v2022.3.4 18 | env: 19 | QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # プロジェクトの除外パス 2 | /target/ 3 | /.idea/ 4 | /.vscode/ 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.space.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * JetBrains Space Automation 3 | * This Kotlin-script file lets you automate build activities 4 | * For more info, see https://www.jetbrains.com/help/space/automation.html 5 | */ 6 | 7 | job("Qodana") { 8 | container("jetbrains/qodana-jvm") { 9 | env["QODANA_TOKEN"] = Secrets("qodana-token") 10 | shellScript { 11 | content = """ 12 | QODANA_REMOTE_URL="ssh://git@git.${'$'}JB_SPACE_API_URL/${'$'}JB_SPACE_PROJECT_KEY/${'$'}JB_SPACE_GIT_REPOSITORY_NAME.git" \ 13 | QODANA_BRANCH=${'$'}JB_SPACE_GIT_BRANCH \ 14 | QODANA_REVISION=${'$'}JB_SPACE_GIT_REVISION \ 15 | qodana 16 | """.trimIndent() 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | ## 参加方法 4 | 5 | 1. Forkする 6 | 2. Branchを作る: `git checkout -b my-new-feature` 7 | 3. テストする: `java -jar TextToSpeak-*.jar` 8 | 4. 変更をコミットする: `git commit -am 'Add some feature'` 9 | 5. Pushする: `git push origin my-new-feature` 10 | 6. Pull Requestを送る 11 | 12 | ## Issues 13 | 14 | 次のIssuesを受け付けています。 15 | 16 | - エラーや問題の報告 17 | => [報告はこちらから](https://github.com/Cosgy-Dev/TextToSpeakBot/issues/new?assignees=&labels=&template=bug-report.md&title=%5B%E3%83%90%E3%82%B0%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88%5D+%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%E3%82%92%E5%85%A5%E5%8A%9B) 18 | - 新機能のリクエスト 19 | => [リクエストはこちらから](https://github.com/Cosgy-Dev/TextToSpeakBot/issues/new?assignees=&labels=&template=feature_request.md&title=%5B%E6%A9%9F%E8%83%BD%E8%A6%81%E6%B1%82%5D%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%E3%82%92%E5%85%A5%E5%8A%9B) 20 | 21 | その他のIssuesは[こちらから](https://github.com/Cosgy-Dev/TextToSpeakBot/issues/new?assignees=&labels=&template=---.md&title=) 22 | 23 | ## Pull Request 24 | 25 | Pull Requestはいつでも歓迎しています。 26 | 27 | **受け入れるPull Request** 28 | 29 | 次の種類のPull Requestを受け付けています。 30 | 31 | - 不具合の修正 32 | - 新機能の追加 33 | - 誤字の修正 34 | - テストの改善 35 | 36 | :memo: **Note:** Pull 37 | Requestが受け入れられるとあなたの貢献が[Contributorsリスト](https://github.com/Cosgy-Dev/TextToSpeakBot/graphs/contributors) 38 | に追加されます。 39 | 40 | **受け入れていないPull Request** 41 | 42 | - [CODE OF CONDUCT](./.github/CODE_OF_CONDUCT.md)に反する内容を含むもの 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Downloads](https://img.shields.io/github/downloads/Cosgy-Dev/TextToSpeakBot/total.svg)](https://github.com/Cosgy-Dev/TextToSpeakBot/releases/latest) 2 | [![Stars](https://img.shields.io/github/stars/Cosgy-Dev/TextToSpeakBot.svg)](https://github.com/Cosgy-Dev/TextToSpeakBot/stargazers) 3 | [![Release](https://img.shields.io/github/release/Cosgy-Dev/TextToSpeakBot.svg)](https://github.com/Cosgy-Dev/TextToSpeakBot/releases/latest) 4 | [![License](https://img.shields.io/github/license/Cosgy-Dev/TextToSpeakBot.svg)](https://github.com/Cosgy-Dev/TextToSpeakBot/blob/master/LICENSE) 5 | [![Discord](https://discordapp.com/api/guilds/497317844191805450/widget.png)](https://discord.gg/RBpkHxf) 6 | [![CircleCI](https://circleci.com/gh/Cosgy-Dev/TextToSpeakBot.svg?style=shield)](https://app.circleci.com/pipelines/github/Cosgy-Dev) 7 | ![Jenkins](https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.cosgy.dev%2Fjob%2FTextToSpeakBot_Dev%2F&label=%E9%96%8B%E7%99%BA%E3%83%93%E3%83%AB%E3%83%89) 8 | ![Jenkins](https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.cosgy.dev%2Fjob%2FTextToSpeakBot%2F&label=%E5%AE%89%E5%AE%9A%E3%83%93%E3%83%AB%E3%83%89) 9 | [![CodeFactor](https://www.codefactor.io/repository/github/cosgy-dev/texttospeakbot/badge)](https://www.codefactor.io/repository/github/cosgy-dev/texttospeakbot) 10 | 11 | # 読み上げボット(TextToSpeakBot) 12 | 13 | 読み上げボットは、Open JTalkを使用したシンプルで操作性の良いボットです。 14 | ユーザーは、自分のオリジナルの声を設定を変更することで変更可能です。 15 | Java言語を使用しており動作が高速で比較的簡単にホストすることが可能です。 16 | 17 | [![Discord Banner 1](https://discordapp.com/api/guilds/497317844191805450/widget.png?style=banner1)](https://discord.gg/RBpkHxf) 18 | 19 | # 機能 20 | 21 | - 辞書機能 22 | - テキストを送信したユーザー名を読み上げ 23 | - ボイスチャンネルに参加、退出したユーザーの名前を読み上げ 24 | - シンプルで使いやすいUI 25 | - ユーザーごとに細かく声の設定が可能 26 | - 翻訳機能 27 | 28 | # インストール方法 29 | 30 | ## セルフホスト 31 | Cosgy Dev公式ページで読み上げボットのインストール方法を紹介しています。 32 | ある程度のスキルを必要としますが、自力で読み上げボットをホストしたい方は[こちら](https://www.cosgy.dev/2021/09/09/post-476/) 33 | でインストール方法を御覧ください。 34 | 35 | ## Docker 36 | 有志の方がDockerイメージを作成してくれています。 37 | Dockerを使用して簡単にホストしたい方は[こちら](https://github.com/masebb/docker-TextToSpeakBot)でインストール方法を御覧ください。 38 | 39 | # 注意事項 40 | - 必ずDiscordの利用規約を守ってください。 41 | - このボットは、自己責任で使用してください。 42 | 43 | # Developer Portalでの設定 44 | - [Developer Portal](https://discord.com/developers/applications)でアプリケーションを作成します。 45 | - Botを作成し、TOKENを取得します。 46 | - config.ymlにTOKENを設定します。 47 | - BotタブからMessage Content Intentを有効にします。 48 | 49 | ![ポータル画像](./img/portal-msg-intent.png) 50 | 51 | - OAuth2でBotの権限を設定し、URLを取得します。 52 | - 取得したURLにアクセスし、サーバーに追加します。 53 | - 以上で設定は完了です。 54 | 55 | Message Content Intentを有効にしないと、ボットがメッセージを読み取れないため、読み上げ機能が正常に動作しません。 56 | 57 | # コントリビューション 58 | このプロジェクトに貢献する方法については、[CONTRIBUTING](CONTRIBUTING.md)を参照してください。 59 | 60 | # サポート 61 | サポートについては、[Cosgy Dev公式サーバー](https://discord.gg/RBpkHxf)で行っています。 62 | 63 | 68 | # ライセンス 69 | このプロジェクトは、Apache License 2.0ライセンスの下でライセンスされています。詳細については、[LICENSE](LICENSE)を参照してください。 -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # セキュリティポリシー 2 | 3 | ## サポートされているバージョン 4 | 5 | 現在、以下のバージョンでセキュリティアップデートがサポートされています。 6 | サポート対象外のバージョンで脆弱性が発見された場合、最新バージョンへのアップデートをお願いします。 7 | 8 | | バージョン | サポート | 9 | |-------|------| 10 | | 0.4.x | ✅ | 11 | | 0.3.1 | ❌ | 12 | | 0.3.0 | ❌ | 13 | | 0.1.x | ❌ | 14 | 15 | ## 脆弱性の報告 16 | 17 | サポートバージョンで脆弱性が発見された際は、DiscordのサポートサーバーまたはGithubのIssuesで報告をお願いします。 -------------------------------------------------------------------------------- /img/portal-msg-intent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cosgy-Dev/TextToSpeakBot/1b066a3109e47c04ae1837506c79ea8cca69eaa6/img/portal-msg-intent.png -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | profile: 3 | name: qodana.starter 4 | include: 5 | - name: CheckDependencyLicenses 6 | -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/Bot.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak 17 | 18 | import com.jagrosh.jdautilities.commons.waiter.EventWaiter 19 | import dev.cosgy.textToSpeak.audio.* 20 | import dev.cosgy.textToSpeak.audio.Dictionary 21 | import dev.cosgy.textToSpeak.gui.GUI 22 | import dev.cosgy.textToSpeak.settings.SettingsManager 23 | import dev.cosgy.textToSpeak.settings.UserSettingsManager 24 | import net.dv8tion.jda.api.JDA 25 | import net.dv8tion.jda.api.entities.Guild 26 | import org.apache.commons.io.FileUtils 27 | import org.slf4j.Logger 28 | import org.slf4j.LoggerFactory 29 | import java.io.File 30 | import java.io.IOException 31 | import java.util.* 32 | import java.util.concurrent.Executors 33 | import java.util.concurrent.ScheduledExecutorService 34 | import java.util.concurrent.TimeUnit 35 | import java.util.function.Consumer 36 | import kotlin.system.exitProcess 37 | 38 | class Bot(val waiter: EventWaiter, val config: BotConfig, val settingsManager: SettingsManager) { 39 | val threadpool: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() 40 | val playerManager: PlayerManager = PlayerManager(this) 41 | val voiceCreation: VoiceCreation 42 | val userSettingsManager: UserSettingsManager 43 | val aloneInVoiceHandler: AloneInVoiceHandler 44 | val englishKanaConversion: EnglishToKatakana 45 | var log: Logger = LoggerFactory.getLogger(this.javaClass) 46 | var dictionary: Dictionary? = null 47 | private set 48 | private var shuttingDown = false 49 | var jda: JDA? = null 50 | private var gui: GUI? = null 51 | 52 | init { 53 | playerManager.init() 54 | voiceCreation = VoiceCreation(this) 55 | userSettingsManager = UserSettingsManager() 56 | aloneInVoiceHandler = AloneInVoiceHandler(this) 57 | aloneInVoiceHandler.init() 58 | englishKanaConversion = EnglishToKatakana(this) 59 | } 60 | 61 | fun readyJDA() { 62 | dictionary = Dictionary.getInstance(this) 63 | } 64 | 65 | fun closeAudioConnection(guildId: Long) { 66 | val guild = jda!!.getGuildById(guildId) 67 | if (guild != null) threadpool.submit { guild.audioManager.closeAudioConnection() } 68 | } 69 | 70 | fun resetGame() { 71 | val game = if (config.game == null || config.game!!.name.lowercase(Locale.getDefault()) 72 | .matches("(none|なし)".toRegex()) 73 | ) null else config.game 74 | if (jda!!.presence.activity != game) jda!!.presence.activity = game 75 | } 76 | 77 | fun shutdown() { 78 | if (shuttingDown) return 79 | shuttingDown = true 80 | if (jda!!.status != JDA.Status.SHUTTING_DOWN) { 81 | jda!!.guilds.forEach(Consumer { g: Guild -> 82 | val am = g.audioManager 83 | if (am.isConnected) { 84 | am.closeAudioConnection() 85 | val ah = am.sendingHandler as AudioHandler? 86 | if (ah != null) { 87 | ah.stopAndClear() 88 | ah.player.destroy() 89 | } 90 | } 91 | }) 92 | 93 | userSettingsManager.closeConnection() 94 | // Wait for any remaining tasks to complete before shutting down the thread pool 95 | threadpool.shutdown() 96 | try { 97 | if (!threadpool.awaitTermination(10, TimeUnit.SECONDS)) { 98 | threadpool.shutdownNow() 99 | if (!threadpool.awaitTermination(10, TimeUnit.SECONDS)) { 100 | log.warn("Thread pool did not terminate") 101 | } 102 | } 103 | } catch (e: InterruptedException) { 104 | threadpool.shutdownNow() 105 | Thread.currentThread().interrupt() 106 | log.warn("Thread pool shutdown was interrupted") 107 | } 108 | } 109 | 110 | // Shutdown JDA 111 | jda?.shutdown() 112 | try { 113 | if (!jda!!.awaitShutdown(10, TimeUnit.SECONDS)) { 114 | log.warn("JDA did not shutdown properly") 115 | } 116 | } catch (e: InterruptedException) { 117 | jda!!.shutdownNow() 118 | Thread.currentThread().interrupt() 119 | log.warn("JDA shutdown was interrupted") 120 | } 121 | 122 | dictionary?.close() 123 | // Delete temporary files 124 | try { 125 | FileUtils.cleanDirectory(File("tmp")) 126 | FileUtils.cleanDirectory(File("wav")) 127 | log.info("Deleted temporary files") 128 | } catch (e: IOException) { 129 | log.warn("Failed to delete temporary files") 130 | } catch (e: IllegalArgumentException) { 131 | log.warn("One or more directory paths were invalid.") 132 | } 133 | 134 | if (gui != null) gui!!.dispose() 135 | exitProcess(0) 136 | } 137 | 138 | 139 | fun setGUI(gui: GUI?) { 140 | this.gui = gui 141 | } 142 | 143 | companion object { 144 | var INSTANCE: Bot? = null 145 | } 146 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/BotConfig.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak 17 | 18 | import com.typesafe.config.ConfigException 19 | import com.typesafe.config.ConfigFactory 20 | import dev.cosgy.textToSpeak.entities.Prompt 21 | import dev.cosgy.textToSpeak.utils.OtherUtil 22 | import net.dv8tion.jda.api.OnlineStatus 23 | import net.dv8tion.jda.api.entities.Activity 24 | import org.apache.commons.io.FileUtils 25 | import java.io.IOException 26 | import java.nio.charset.StandardCharsets 27 | import java.nio.file.Path 28 | import kotlin.system.exitProcess 29 | 30 | class BotConfig(private val prompt: Prompt) { 31 | private var path: Path? = null 32 | var token: String? = null 33 | private set 34 | var prefix: String? = null 35 | private set 36 | private var altprefix: String? = null 37 | var dictionary: String? = null 38 | private set 39 | var voiceDirectory: String? = null 40 | private set 41 | var winJTalkDir: String? = null 42 | private set 43 | var ownerId: Long = 0 44 | private set 45 | var aloneTimeUntilStop: Long = 0 46 | private set 47 | var maxMessageCount = 0 48 | private set 49 | var status: OnlineStatus? = null 50 | private set 51 | var game: Activity? = null 52 | private set 53 | private var updatealerts = false 54 | private var dBots = false 55 | var helpToDm = false 56 | private set 57 | var isValid = false 58 | private set 59 | 60 | var isForceUTF8 = false 61 | private set 62 | 63 | var deeplApiKey: String? = null 64 | private set 65 | 66 | fun load() { 67 | isValid = false 68 | try { 69 | path = OtherUtil.getPath(System.getProperty("config.file", System.getProperty("config", "config.txt"))) 70 | if (path!!.toFile().exists()) { 71 | if (System.getProperty("config.file") == null) System.setProperty( 72 | "config.file", 73 | System.getProperty("config", "config.txt") 74 | ) 75 | ConfigFactory.invalidateCaches() 76 | } 77 | 78 | // load in the config file, plus the default values 79 | //Config = ConfigFactory.parseFile(path.toFile()).withFallback(ConfigFactory.load()); 80 | val config = ConfigFactory.load() 81 | token = config.getString("token") 82 | prefix = config.getString("prefix") 83 | altprefix = config.getString("altprefix") 84 | ownerId = if (config.getAnyRef("owner") is String) 0L else config.getLong("owner") 85 | game = OtherUtil.parseGame(config.getString("game")) 86 | status = OtherUtil.parseStatus(config.getString("status")) 87 | updatealerts = config.getBoolean("updatealerts") 88 | dictionary = config.getString("dictionary") 89 | voiceDirectory = config.getString("voiceDirectory") 90 | aloneTimeUntilStop = config.getLong("alonetimeuntilstop") 91 | maxMessageCount = config.getInt("maxmessagecount") 92 | winJTalkDir = config.getString("winjtalkdir") 93 | helpToDm = config.getBoolean("helptodm") 94 | isForceUTF8 = config.getBoolean("forceutf-8") 95 | deeplApiKey = config.getString("deeplapikey") 96 | dBots = ownerId == 334091398263341056 97 | var write = false 98 | 99 | // validate bot token 100 | if (token == null || token!!.isEmpty() || token!!.matches("(BOT_TOKEN_HERE|ボットトークンをここに貼り付け)".toRegex())) { 101 | token = prompt.prompt( 102 | """ 103 | ボットトークンを入力してください。 104 | ボットトークン: 105 | """.trimIndent() 106 | ) 107 | write = if (token == null) { 108 | prompt.alert( 109 | Prompt.Level.WARNING, CONTEXT, 110 | """ 111 | トークンが入力されていません!終了します。 112 | 113 | 設定ファイルの場所: ${path!!.toAbsolutePath()} 114 | """.trimIndent() 115 | ) 116 | return 117 | } else { 118 | true 119 | } 120 | } 121 | 122 | // validate bot owner 123 | if (ownerId <= 0) { 124 | ownerId = try { 125 | prompt.prompt( 126 | """ 127 | 所有者のユーザーIDが設定されていない、または有効なIDではありません。 128 | ボットの所有者のユーザーIDを入力してください。 129 | 所有者のユーザーID: 130 | """.trimIndent() 131 | )!!.toLong() 132 | } catch (ex: NumberFormatException) { 133 | 0 134 | } catch (ex: NullPointerException) { 135 | 0 136 | } 137 | if (ownerId <= 0) { 138 | prompt.alert( 139 | Prompt.Level.ERROR, CONTEXT, 140 | """ 141 | 無効なユーザーIDです!終了します。 142 | 設定ファイルの場所: ${path!!.toAbsolutePath()} 143 | """.trimIndent() 144 | ) 145 | exitProcess(0) 146 | } else { 147 | write = true 148 | } 149 | } 150 | if (write) { 151 | val original = OtherUtil.loadResource(this, "/reference.conf") 152 | val mod: String = 153 | original?.substring(original.indexOf(START_TOKEN) + START_TOKEN.length, original.indexOf(END_TOKEN)) 154 | ?.replace("BOT_TOKEN_HERE", token!!)?.replace("ボットトークンをここに貼り付け", token!!) 155 | ?.replace("0 // OWNER ID", ownerId.toString()) 156 | ?.replace("所有者IDをここに貼り付け", ownerId.toString())?.trim { it <= ' ' } 157 | ?: """ 158 | token = $token 159 | owner = $ownerId 160 | """.trimIndent() 161 | FileUtils.writeStringToFile(path!!.toFile(), mod, StandardCharsets.UTF_8) 162 | } 163 | 164 | // if we get through the whole config, it's good to go 165 | isValid = true 166 | } catch (ex: ConfigException) { 167 | prompt.alert( 168 | Prompt.Level.ERROR, CONTEXT, 169 | """ 170 | $ex: ${ex.message} 171 | 172 | 設定ファイルの場所: ${path!!.toAbsolutePath()} 173 | """.trimIndent() 174 | ) 175 | } catch (ex: IOException) { 176 | prompt.alert( 177 | Prompt.Level.ERROR, CONTEXT, 178 | """ 179 | $ex: ${ex.message} 180 | 181 | 設定ファイルの場所: ${path!!.toAbsolutePath()} 182 | """.trimIndent() 183 | ) 184 | } 185 | } 186 | 187 | val configLocation: String 188 | get() = path!!.toFile().absolutePath 189 | val altPrefix: String? 190 | get() = if ("NONE".equals(altprefix, ignoreCase = true)) null else altprefix 191 | 192 | fun useUpdateAlerts(): Boolean { 193 | return updatealerts 194 | } 195 | 196 | companion object { 197 | private const val CONTEXT = "Config" 198 | private const val START_TOKEN = "/// START OF TEXT TO SPEAK BOT CONFIG ///" 199 | private const val END_TOKEN = "/// END OF TEXT TO SPEAK BOT CONFIG ///" 200 | } 201 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/Listener.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak 17 | 18 | import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler 19 | import com.sedmelluq.discord.lavaplayer.tools.FriendlyException 20 | import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist 21 | import com.sedmelluq.discord.lavaplayer.track.AudioTrack 22 | import dev.cosgy.textToSpeak.audio.AudioHandler 23 | import dev.cosgy.textToSpeak.audio.QueuedTrack 24 | import dev.cosgy.textToSpeak.utils.OtherUtil 25 | import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel 26 | import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent 27 | import net.dv8tion.jda.api.events.session.ReadyEvent 28 | import net.dv8tion.jda.api.events.session.ShutdownEvent 29 | import net.dv8tion.jda.api.hooks.ListenerAdapter 30 | import org.slf4j.Logger 31 | import org.slf4j.LoggerFactory 32 | import java.io.IOException 33 | import java.util.* 34 | import java.util.concurrent.TimeUnit 35 | 36 | class Listener(private val bot: Bot) : ListenerAdapter() { 37 | var log: Logger = LoggerFactory.getLogger(this.javaClass) 38 | override fun onReady(event: ReadyEvent) { 39 | if (event.jda.guilds.isEmpty()) { 40 | val log = LoggerFactory.getLogger("TTSBot") 41 | log.warn("このボットはグループに入っていません!ボットをあなたのグループに追加するには、以下のリンクを使用してください。") 42 | log.warn(event.jda.getInviteUrl(*TextToSpeak.RECOMMENDED_PERMS)) 43 | } 44 | if (bot.config.useUpdateAlerts()) { 45 | bot.threadpool.scheduleWithFixedDelay({ 46 | val owner = bot.jda?.getUserById(bot.config.ownerId) 47 | if (owner != null) { 48 | val currentVersion = OtherUtil.currentVersion 49 | // 現在のバージョンがリリース版かを確認 50 | if (!OtherUtil.isBetaVersion(currentVersion)) { 51 | // リリースバージョンの場合 52 | val latestVersion = OtherUtil.latestVersion 53 | if (latestVersion != null && !currentVersion.equals( 54 | latestVersion, 55 | ignoreCase = true 56 | ) && TextToSpeak.CHECK_UPDATE 57 | ) { 58 | val msg = String.format(OtherUtil.NEW_VERSION_AVAILABLE, currentVersion, latestVersion) 59 | owner.openPrivateChannel().queue { pc: PrivateChannel -> pc.sendMessage(msg).queue() } 60 | } 61 | } else { 62 | // ベータバージョンの場合 63 | val latestBeta = OtherUtil.latestBetaVersion 64 | if (latestBeta != null && OtherUtil.compareVersions(currentVersion, latestBeta) != 0) { 65 | val msg = String.format( 66 | OtherUtil.NEW_BETA_VERSION_AVAILABLE, currentVersion, 67 | OtherUtil.latestBetaVersion, 68 | OtherUtil.latestBetaVersion 69 | ) 70 | owner.openPrivateChannel().queue { pc: PrivateChannel -> pc.sendMessage(msg).queue() } 71 | } 72 | } 73 | } 74 | }, 0, 24, TimeUnit.HOURS) 75 | } 76 | } 77 | 78 | override fun onGuildVoiceUpdate(event: GuildVoiceUpdateEvent) { 79 | bot.aloneInVoiceHandler.onVoiceUpdate(event) 80 | 81 | val botMember = event.guild.selfMember 82 | val settings = bot.settingsManager.getSettings(event.guild) 83 | if (event.channelLeft != null) { 84 | if (settings.isJoinAndLeaveRead() && Objects.requireNonNull(event.guild.selfMember.voiceState)?.channel === event.channelLeft && event.channelLeft!!.members.size > 1) { 85 | val file: String? = try { 86 | var nic = event.member.nickname 87 | nic = nic ?: event.member.effectiveName 88 | bot.voiceCreation.createVoice( 89 | event.guild, 90 | event.member.user, 91 | "${if (settings.isReadNic()) nic else event.member.effectiveName}がボイスチャンネルから退出しました。" 92 | ) 93 | } catch (e: IOException) { 94 | throw RuntimeException(e) 95 | } catch (e: InterruptedException) { 96 | throw RuntimeException(e) 97 | } 98 | bot.playerManager.loadItemOrdered(event.guild, file, ResultHandler(event)) 99 | } 100 | if (event.channelLeft!!.members.size == 1 && event.channelLeft!!.members.contains(botMember)) { 101 | val handler = event.guild.audioManager.sendingHandler as AudioHandler? 102 | handler!!.queue.clear() 103 | try { 104 | bot.voiceCreation.clearGuildFolder(event.guild) 105 | } catch (e: IOException) { 106 | throw RuntimeException(e) 107 | } 108 | } 109 | } 110 | if (event.channelJoined != null) { 111 | if (settings.isJoinAndLeaveRead() && Objects.requireNonNull(event.guild.selfMember.voiceState)?.channel === event.channelJoined) { 112 | val file: String? = try { 113 | var nic = event.member.nickname 114 | nic = nic ?: event.member.effectiveName 115 | 116 | bot.voiceCreation.createVoice( 117 | event.guild, 118 | event.member.user, 119 | "${if (settings.isReadNic()) nic else event.member.effectiveName}がボイスチャンネルに参加しました。" 120 | ) 121 | } catch (e: IOException) { 122 | throw RuntimeException(e) 123 | } catch (e: InterruptedException) { 124 | throw RuntimeException(e) 125 | } 126 | bot.playerManager.loadItemOrdered(event.guild, file, ResultHandler(event)) 127 | } 128 | } 129 | } 130 | 131 | override fun onShutdown(event: ShutdownEvent) { 132 | bot.shutdown() 133 | } 134 | 135 | private inner class ResultHandler(private val event: GuildVoiceUpdateEvent) : AudioLoadResultHandler { 136 | private fun loadSingle(track: AudioTrack) { 137 | val handler = event.guild.audioManager.sendingHandler as AudioHandler? 138 | handler!!.addTrack(QueuedTrack(track, event.member.user)) 139 | } 140 | 141 | override fun trackLoaded(track: AudioTrack) { 142 | loadSingle(track) 143 | } 144 | 145 | override fun playlistLoaded(playlist: AudioPlaylist) {} 146 | override fun noMatches() {} 147 | override fun loadFailed(throwable: FriendlyException) {} 148 | } 149 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/audio/AloneInVoiceHandler.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.audio 17 | 18 | import dev.cosgy.textToSpeak.Bot 19 | import net.dv8tion.jda.api.entities.Guild 20 | import net.dv8tion.jda.api.entities.Member 21 | import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent 22 | import java.time.Instant 23 | import java.util.concurrent.TimeUnit 24 | import java.util.function.Consumer 25 | 26 | class AloneInVoiceHandler(private val bot: Bot) { 27 | private val aloneSince = HashMap() 28 | private var aloneTimeUntilStop: Long = 0 29 | fun init() { 30 | aloneTimeUntilStop = bot.config.aloneTimeUntilStop 31 | if (aloneTimeUntilStop > 0) bot.threadpool.scheduleWithFixedDelay({ this.check() }, 0, 5, TimeUnit.SECONDS) 32 | } 33 | 34 | private fun check() { 35 | val toRemove: MutableSet = HashSet() 36 | for ((key, value) in aloneSince) { 37 | if (value.epochSecond > Instant.now().epochSecond - aloneTimeUntilStop) continue 38 | val guild = bot.jda?.getGuildById(key) 39 | if (guild == null) { 40 | toRemove.add(key) 41 | continue 42 | } 43 | (guild.audioManager.sendingHandler as AudioHandler?)!!.stopAndClear() 44 | guild.audioManager.closeAudioConnection() 45 | toRemove.add(key) 46 | } 47 | toRemove.forEach(Consumer { key: Long -> aloneSince.remove(key) }) 48 | } 49 | 50 | fun onVoiceUpdate(event: GuildVoiceUpdateEvent) { 51 | if (aloneTimeUntilStop <= 0) return 52 | val guild = event.entity.guild 53 | if (!bot.playerManager.hasHandler(guild)) return 54 | val alone = isAlone(guild) 55 | val inList = aloneSince.containsKey(guild.idLong) 56 | if (!alone && inList) aloneSince.remove(guild.idLong) else if (alone && !inList) aloneSince[guild.idLong] = 57 | Instant.now() 58 | } 59 | 60 | private fun isAlone(guild: Guild): Boolean { 61 | return if (guild.audioManager.connectedChannel == null) false else guild.audioManager.connectedChannel!!.members.stream() 62 | .noneMatch { x: Member -> 63 | (!x.voiceState!!.isDeafened 64 | && !x.user.isBot) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/audio/AudioHandler.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.audio 17 | 18 | import com.sedmelluq.discord.lavaplayer.player.AudioPlayer 19 | import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter 20 | import com.sedmelluq.discord.lavaplayer.track.AudioTrack 21 | import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason 22 | import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame 23 | import dev.cosgy.textToSpeak.queue.FairQueue 24 | import net.dv8tion.jda.api.JDA 25 | import net.dv8tion.jda.api.audio.AudioSendHandler 26 | import net.dv8tion.jda.api.entities.Guild 27 | import java.nio.ByteBuffer 28 | 29 | class AudioHandler(guild: Guild, val player: AudioPlayer) : AudioEventAdapter(), AudioSendHandler { 30 | val queue = FairQueue() 31 | val votes: Set = HashSet() 32 | private val guildId: Long 33 | private val stringGuildId: String 34 | private var lastFrame: AudioFrame? = null 35 | 36 | init { 37 | guildId = guild.idLong 38 | stringGuildId = guild.id 39 | } 40 | 41 | /** 42 | * 再生キューに追加 43 | */ 44 | fun addTrack(qtrack: QueuedTrack): Int { 45 | return if (player.playingTrack == null) { 46 | player.playTrack(qtrack.track) 47 | -1 48 | } else queue.add(qtrack) 49 | } 50 | 51 | fun stopAndClear() { 52 | queue.clear() 53 | player.stopTrack() 54 | } 55 | 56 | fun isVoiceListening(jda: JDA): Boolean { 57 | return guild(jda)!!.selfMember.voiceState!!.inAudioChannel() && player.playingTrack != null 58 | } 59 | 60 | val requester: Long 61 | get() = if (player.playingTrack == null || player.playingTrack.getUserData(Long::class.java) == null) 0 else player.playingTrack.getUserData( 62 | Long::class.java 63 | ) 64 | 65 | // Audio Events 66 | override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) { 67 | if (!queue.isEmpty) { 68 | val qt = queue.pull() 69 | player.playTrack(qt!!.track) 70 | } 71 | } 72 | 73 | // Audio Send Handler methods 74 | override fun canProvide(): Boolean { 75 | lastFrame = player.provide() 76 | return lastFrame != null 77 | } 78 | 79 | override fun provide20MsAudio(): ByteBuffer? { 80 | return ByteBuffer.wrap(lastFrame!!.data) 81 | } 82 | 83 | override fun isOpus(): Boolean { 84 | return true 85 | } 86 | 87 | // Private methods 88 | private fun guild(jda: JDA): Guild? { 89 | return jda.getGuildById(guildId) 90 | } 91 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/audio/Dictionary.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.audio 17 | 18 | import dev.cosgy.textToSpeak.Bot 19 | import dev.cosgy.textToSpeak.utils.OtherUtil 20 | import org.slf4j.LoggerFactory 21 | import java.nio.file.Path 22 | import java.sql.Connection 23 | import java.sql.DriverManager 24 | import java.sql.SQLException 25 | import java.util.* 26 | import java.util.concurrent.ConcurrentHashMap 27 | import java.util.function.BiFunction 28 | 29 | class Dictionary private constructor(bot: Bot) { 30 | private val logger = LoggerFactory.getLogger(this.javaClass) 31 | private val path: Path? 32 | private val create: Boolean 33 | private var connection: Connection 34 | private val guildDic: ConcurrentHashMap> = ConcurrentHashMap() 35 | 36 | init { 37 | path = OtherUtil.getPath("UserData.sqlite") 38 | create = !path.toFile().exists() 39 | try { 40 | Class.forName("org.sqlite.JDBC") 41 | connection = DriverManager.getConnection("jdbc:sqlite:UserData.sqlite") 42 | val statement = connection.createStatement() 43 | statement.executeUpdate("CREATE TABLE IF NOT EXISTS Dictionary(guild_id integer,word text,reading)") 44 | val guilds = bot.jda!!.guilds 45 | for (value in guilds) { 46 | val guildId = value.idLong 47 | val optionalHashMap = getWordsFromDatabase(guildId) 48 | optionalHashMap.ifPresent { hashMap: HashMap -> guildDic[guildId] = hashMap } 49 | } 50 | } catch (e: SQLException) { 51 | logger.error("An error occurred while initializing the dictionary: ", e) 52 | throw IllegalStateException(e) 53 | } catch (e: ClassNotFoundException) { 54 | logger.error("An error occurred while initializing the dictionary: ", e) 55 | throw IllegalStateException(e) 56 | } 57 | logger.info("Dictionary initialization completed.") 58 | } 59 | 60 | /** 61 | * データベースとHashMapの内容を更新または新規追加します。 62 | * 63 | * @param guildId サーバーID 64 | * @param word 単語 65 | * @param reading 読み方 66 | */ 67 | @Synchronized 68 | fun updateDictionary(guildId: Long, word: String?, reading: String?) { 69 | guildDic.compute(guildId) { _: Long?, v: HashMap? -> 70 | val words: HashMap = v ?: HashMap() 71 | words[word] = reading 72 | executeUpdate(guildId, word, reading) 73 | words 74 | } 75 | } 76 | 77 | /** 78 | * データベースに登録されている単語を削除します。 79 | * 80 | * @param guildId サーバーID 81 | * @param word 単語 82 | * @return 正常に削除できた場合は `true`、削除時に問題が発生した場合は`false`を返します。 83 | */ 84 | @Synchronized 85 | fun deleteDictionary(guildId: Long, word: String?): Boolean { 86 | guildDic.compute(guildId, BiFunction { _: Long?, v: HashMap? -> 87 | if (v == null || !v.containsKey(word)) { 88 | return@BiFunction null 89 | } 90 | val words = HashMap(v) 91 | words.remove(word) 92 | executeDelete(guildId, word) 93 | words 94 | }) 95 | return true 96 | } 97 | 98 | /** 99 | * サーバーの辞書データを取得します。 100 | * 101 | * @param guildId サーバーID 102 | * @return `HashMap`形式の変数を返します。 103 | */ 104 | fun getWords(guildId: Long): HashMap { 105 | return guildDic.getOrDefault(guildId, HashMap()) 106 | } 107 | 108 | private fun getWordsFromDatabase(guildId: Long): Optional> { 109 | val sql = "SELECT * FROM Dictionary WHERE guild_id = ? ORDER BY LENGTH(word) DESC" 110 | try { 111 | connection.prepareStatement(sql).use { ps -> 112 | ps.setLong(1, guildId) 113 | val rs = ps.executeQuery() 114 | val word = HashMap() 115 | while (rs.next()) { 116 | word[rs.getString(2)] = rs.getString(3) 117 | } 118 | return Optional.of(word) 119 | } 120 | } catch (throwables: SQLException) { 121 | logger.error("An error occurred while retrieving data from the dictionary: ", throwables) 122 | return Optional.empty() 123 | } 124 | } 125 | 126 | private fun executeUpdate(guildId: Long, word: String?, reading: String?) { 127 | val sql = "INSERT OR REPLACE INTO Dictionary(guild_id, word, reading) VALUES (?,?,?)" 128 | try { 129 | connection.prepareStatement(sql).use { ps -> 130 | ps.setLong(1, guildId) 131 | ps.setString(2, word) 132 | ps.setString(3, reading) 133 | ps.executeUpdate() 134 | } 135 | } catch (throwables: SQLException) { 136 | logger.error("An error occurred while updating the dictionary: ", throwables) 137 | } 138 | } 139 | 140 | private fun executeDelete(guildId: Long, word: String?) { 141 | val sql = "DELETE FROM Dictionary WHERE guild_id = ? AND word = ?" 142 | try { 143 | connection.prepareStatement(sql).use { ps -> 144 | ps.setLong(1, guildId) 145 | ps.setString(2, word) 146 | ps.executeUpdate() 147 | } 148 | } catch (throwables: SQLException) { 149 | logger.error("An error occurred while deleting from the dictionary: ", throwables) 150 | } 151 | } 152 | 153 | fun close() { 154 | try { 155 | connection.close() 156 | } catch (throwables: SQLException) { 157 | logger.error("An error occurred while closing the database connection: ", throwables) 158 | } 159 | } 160 | 161 | companion object { 162 | private var instance: Dictionary? = null 163 | fun getInstance(bot: Bot): Dictionary? { 164 | if (instance == null) { 165 | instance = Dictionary(bot) 166 | } 167 | return instance 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/audio/EnglishToKatakana.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.audio 17 | 18 | import dev.cosgy.textToSpeak.Bot 19 | import org.slf4j.LoggerFactory 20 | import java.io.BufferedReader 21 | import java.io.InputStreamReader 22 | import java.util.* 23 | 24 | class EnglishToKatakana(bot: Bot) { 25 | private val logger = LoggerFactory.getLogger(this.javaClass) 26 | 27 | // 辞書ファイルの名前 28 | private val fileName = "bep-eng.dic" 29 | private val map = TreeMap() 30 | 31 | init { 32 | logger.info("英語->カタカナ変換辞書を読み込みます。") 33 | val inputStream = object {}.javaClass.getResourceAsStream("/$fileName") 34 | val reader = BufferedReader(InputStreamReader(inputStream!!)) 35 | 36 | reader.useLines { lines -> 37 | lines.forEach { line -> 38 | val parts = line.split(" ", limit = 2) 39 | if (parts.size == 2) { 40 | val key = parts[0] 41 | val value = parts[1] 42 | map[key] = value 43 | } 44 | } 45 | } 46 | 47 | logger.info("英語->カタカナ変換辞書の読み込みが完了しました。") 48 | } 49 | 50 | fun convert(text: String): String { 51 | val startTime = System.currentTimeMillis() 52 | val words = text.split("\\s+".toRegex()) // 空白文字で単語を区切る 53 | 54 | val result = StringBuilder() 55 | 56 | for (word in words) { 57 | val replacement = searchWord(word.uppercase()) 58 | if (replacement != null) { 59 | result.append(replacement) 60 | } else { 61 | result.append(word) 62 | } 63 | result.append(" ") 64 | } 65 | 66 | val endTime = System.currentTimeMillis() 67 | val executionTime = endTime - startTime 68 | logger.debug("探索処理時間: $executionTime ミリ秒") 69 | 70 | 71 | return result.toString().trim() 72 | } 73 | 74 | private fun searchWord(word: String): String? { 75 | return map[word] 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/audio/PlayerManager.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.audio 17 | 18 | import com.sedmelluq.discord.lavaplayer.player.AudioConfiguration 19 | import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager 20 | import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.entities.Guild 23 | import org.slf4j.LoggerFactory 24 | 25 | class PlayerManager(val bot: Bot) : DefaultAudioPlayerManager() { 26 | private val logger = LoggerFactory.getLogger(this.javaClass) 27 | fun init() { 28 | AudioSourceManagers.registerLocalSource(this) 29 | if (configuration.opusEncodingQuality != 10) { 30 | logger.debug("OpusEncodingQuality は、" + configuration.opusEncodingQuality + "(< 10)" + ", 品質を10に設定します。") 31 | configuration.opusEncodingQuality = 10 32 | } 33 | if (configuration.resamplingQuality != AudioConfiguration.ResamplingQuality.HIGH) { 34 | logger.debug("ResamplingQuality は " + configuration.resamplingQuality.name + "(HIGHではない), 品質をHIGHに設定します。") 35 | configuration.resamplingQuality = AudioConfiguration.ResamplingQuality.HIGH 36 | } 37 | } 38 | 39 | fun hasHandler(guild: Guild): Boolean { 40 | return guild.audioManager.sendingHandler != null 41 | } 42 | 43 | fun setUpHandler(guild: Guild): AudioHandler? { 44 | val handler: AudioHandler? 45 | if (guild.audioManager.sendingHandler == null) { 46 | val player = createPlayer() 47 | player.volume = bot.settingsManager.getSettings(guild).volume 48 | handler = AudioHandler(guild, player) 49 | player.addListener(handler) 50 | guild.audioManager.sendingHandler = handler 51 | guild.audioManager.isSelfMuted = true 52 | } else handler = guild.audioManager.sendingHandler as AudioHandler? 53 | return handler 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/audio/QueuedTrack.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.audio 17 | 18 | import com.sedmelluq.discord.lavaplayer.track.AudioTrack 19 | import dev.cosgy.textToSpeak.queue.Queueable 20 | import net.dv8tion.jda.api.entities.User 21 | 22 | class QueuedTrack(val track: AudioTrack, owner: Long) : Queueable { 23 | 24 | constructor(track: AudioTrack, owner: User) : this(track, owner.idLong) 25 | 26 | init { 27 | track.userData = owner 28 | } 29 | 30 | override val identifier: Long 31 | get() = track.getUserData(Long::class.java) 32 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/audio/VoiceCreation.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.audio 17 | 18 | import com.ibm.icu.text.Transliterator 19 | import dev.cosgy.textToSpeak.Bot 20 | import dev.cosgy.textToSpeak.settings.UserSettings 21 | import net.dv8tion.jda.api.entities.* 22 | import org.apache.commons.io.FileUtils 23 | import org.slf4j.LoggerFactory 24 | import java.io.* 25 | import java.nio.file.Files 26 | import java.nio.file.Path 27 | import java.nio.file.Paths 28 | import java.text.BreakIterator 29 | import java.util.* 30 | 31 | class VoiceCreation( // 各種設定の値を保持するためのフィールド 32 | private val bot: Bot 33 | ) { 34 | private val dictionary: String? = bot.config.dictionary 35 | private val voiceDirectory: String? = bot.config.voiceDirectory 36 | private val winJTalkDir: String? = bot.config.winJTalkDir 37 | private val maxMessageCount: Int = bot.config.maxMessageCount 38 | 39 | @Throws(IOException::class, InterruptedException::class) 40 | fun createVoice(guild: Guild, user: User, message: String): String { 41 | val startTime = System.currentTimeMillis() 42 | // ファイル名やパスの生成に使用するIDを生成する 43 | val guildId = guild.id 44 | val fileId = UUID.randomUUID().toString() 45 | val fileName = "wav" + File.separator + guildId + File.separator + fileId + ".wav" 46 | 47 | // 必要なディレクトリを作成する 48 | createDirectories(guildId) 49 | 50 | // ユーザーの設定を取得する 51 | val settings = bot.userSettingsManager.getSettings(user.idLong) 52 | 53 | // 辞書データを取得し、メッセージを変換する 54 | val words = bot.dictionary?.getWords(guild.idLong) 55 | var dicMsg = sanitizeMessage(message) 56 | for ((key, value) in words!!) { 57 | dicMsg = dicMsg.replace(key!!, value!!) 58 | } 59 | 60 | // スポイラーを処理する 61 | dicMsg = processSpoilers(dicMsg) 62 | // 英語をカタカナに変換する 63 | dicMsg = bot.englishKanaConversion.convert(dicMsg) 64 | dicMsg = toKatakanaIfEnglishExists(dicMsg) 65 | 66 | 67 | val tmpFilePath = createTmpTextFile(guildId, fileId, dicMsg.replace("\n", "")) 68 | 69 | // コマンドを生成して実行する 70 | val command = getCommand(settings, tmpFilePath, fileName) 71 | val builder = ProcessBuilder(*command) 72 | builder.redirectErrorStream(true) 73 | logger.debug("Command: " + java.lang.String.join(" ", *command)) 74 | val process = builder.start() 75 | process.waitFor() 76 | 77 | val endTime = System.currentTimeMillis() 78 | val executionTime = endTime - startTime 79 | logger.debug("読み上げ処理時間: $executionTime ミリ秒") 80 | 81 | return fileName 82 | } 83 | 84 | /** 85 | * メッセージ内のスポイラーを処理するメソッド 86 | */ 87 | private fun processSpoilers(input: String): String { 88 | 89 | val regex = Regex("""\|\|([^|]+)\|\|""") 90 | return regex.replace(input) { 91 | "スポイラー" 92 | } 93 | } 94 | 95 | /** 96 | * 英単語をカタカナに変換するメソッド 97 | */ 98 | private fun toKatakanaIfEnglishExists(message: String): String { 99 | var englishExists = false 100 | for (c in message) { 101 | if (c in 'a'..'z' || c in 'A'..'Z') { 102 | englishExists = true 103 | break 104 | } 105 | } 106 | return if (englishExists) { 107 | val latinToKatakana = Transliterator.getInstance("Latin-Katakana") 108 | latinToKatakana.transliterate(message) 109 | } else { 110 | message 111 | } 112 | } 113 | 114 | // メッセージをサニタイズするメソッド 115 | private fun sanitizeMessage(message: String): String { 116 | var sanitizedMsg = message.replace("[\\uD800-\\uDFFF]".toRegex(), " ") 117 | sanitizedMsg = sanitizedMsg.replace("Kosugi_kun", "コスギクン") 118 | val sentences = BreakIterator.getSentenceInstance(Locale.JAPANESE) 119 | sentences.setText(sanitizedMsg) 120 | var messageCount = 0 121 | var lastIndex = 0 122 | val builder = StringBuilder() 123 | while (sentences.next() != BreakIterator.DONE) { 124 | val sentence = sanitizedMsg.substring(lastIndex, sentences.current()) 125 | if (maxMessageCount > 0 && sentence.length + builder.length > maxMessageCount) { 126 | builder.append("以下略") 127 | break 128 | } 129 | builder.append(sentence) 130 | builder.append("\n") 131 | messageCount++ 132 | lastIndex = sentences.current() 133 | } 134 | return builder.toString() 135 | } 136 | 137 | // テキストファイルを作成するメソッド 138 | @Throws(FileNotFoundException::class, UnsupportedEncodingException::class) 139 | private fun createTmpTextFile(guildId: String, fileId: String, message: String): String { 140 | val filePath = Paths.get("tmp", guildId, "$fileId.txt").toString() 141 | PrintWriter(filePath, characterCode).use { it.write(message) } 142 | return filePath 143 | } 144 | 145 | private val characterCode: String 146 | // 文字コードを取得するメソッド 147 | get() { 148 | if (!IS_WINDOWS) return "UTF-8" 149 | 150 | return if (bot.config.isForceUTF8) "UTF-8" else "Shift-JIS" 151 | } 152 | 153 | // コマンドを生成するメソッド 154 | private fun getCommand(settings: UserSettings?, tmpFilePath: String, fileName: String): Array { 155 | val command = ArrayList() 156 | command.add(openJTalkExecutable) 157 | command.add("-x") 158 | command.add(dictionary) 159 | command.add("-m") 160 | command.add(getVoiceFilePath(settings!!.voiceSetting)) 161 | command.add("-ow") 162 | command.add(fileName) 163 | command.add("-r") 164 | command.add(settings.speedSetting.toString()) 165 | command.add("-jf") 166 | command.add(settings.intonationSetting.toString()) 167 | command.add("-a") 168 | command.add(settings.voiceQualityASetting.toString()) 169 | command.add("-fm") 170 | command.add(settings.voiceQualityFmSetting.toString()) 171 | command.add(tmpFilePath) 172 | return command.toTypedArray() 173 | } 174 | 175 | private val openJTalkExecutable: String? 176 | get() = if (IS_WINDOWS) { 177 | winJTalkDir?.let { Paths.get(it, "open_jtalk.exe").toString() } 178 | } else { 179 | "open_jtalk" 180 | } 181 | 182 | private fun getVoiceFilePath(voice: String?): String? { 183 | return voiceDirectory?.let { Paths.get(it, "$voice.htsvoice").toString() } 184 | } 185 | 186 | // 必要なディレクトリを作成するメソッド 187 | 188 | @Throws(IOException::class) 189 | private fun createDirectories(guildId: String) { 190 | val tmpPath: Path = Paths.get("tmp", guildId) 191 | val wavPath: Path = Paths.get("wav", guildId) 192 | Files.createDirectories(tmpPath) 193 | Files.createDirectories(wavPath) 194 | } 195 | 196 | // ギルドに関連する一時ファイルや音声ファイルを削除するメソッド 197 | @Throws(IOException::class) 198 | fun clearGuildFolder(guild: Guild) { 199 | val guildId = guild.id 200 | val tmpPath = Paths.get("tmp" + File.separator + guildId) 201 | val wavPath = Paths.get("wav" + File.separator + guildId) 202 | if (Files.exists(tmpPath)) { 203 | FileUtils.cleanDirectory(tmpPath.toFile()) 204 | logger.info("Cleared temporary files for guild: $guildId") 205 | } 206 | if (Files.exists(wavPath)) { 207 | FileUtils.cleanDirectory(wavPath.toFile()) 208 | logger.info("Cleared WAV files for guild: $guildId") 209 | } 210 | } 211 | 212 | val voices: List 213 | get() { 214 | val voiceDir = File(requireNotNull(voiceDirectory) { "voiceDirectory is null" }) 215 | return voiceDir.listFiles { _, name -> name.endsWith(".htsvoice") } 216 | ?.map { file -> file.nameWithoutExtension } 217 | ?.toList() 218 | .orEmpty() 219 | .also { logger.debug("Available voices: {}", it) } 220 | } 221 | 222 | 223 | companion object { 224 | private val logger = LoggerFactory.getLogger(VoiceCreation::class.java) 225 | private val IS_WINDOWS = System.getProperty("os.name").lowercase(Locale.getDefault()).startsWith("win") 226 | } 227 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/AdminCommand.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands 17 | 18 | import com.jagrosh.jdautilities.command.CommandClient 19 | import com.jagrosh.jdautilities.command.CommandEvent 20 | import com.jagrosh.jdautilities.command.SlashCommand 21 | import com.jagrosh.jdautilities.command.SlashCommandEvent 22 | import net.dv8tion.jda.api.Permission 23 | import java.util.function.Predicate 24 | 25 | abstract class AdminCommand : SlashCommand() { 26 | init { 27 | this.category = Category("管理", Predicate { event: CommandEvent -> 28 | if (event.isOwner || event.member.isOwner) return@Predicate true 29 | if (event.guild == null) return@Predicate true 30 | event.member.hasPermission(Permission.MANAGE_SERVER) 31 | }) 32 | guildOnly = true 33 | userPermissions = arrayOf(Permission.ADMINISTRATOR) 34 | } 35 | 36 | companion object { 37 | fun checkAdminPermission(client: CommandClient, event: SlashCommandEvent): Boolean { 38 | if (event.user.id == client.ownerId || event.member!!.isOwner) return true 39 | return if (event.guild == null) true else event.member!!.hasPermission(Permission.MANAGE_SERVER) 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/OwnerCommand.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands 17 | 18 | import com.jagrosh.jdautilities.command.SlashCommand 19 | 20 | abstract class OwnerCommand : SlashCommand() { 21 | init { 22 | this.category = Category("Owner") 23 | ownerCommand = true 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/admin/GuildSettings.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.admin 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommandEvent 20 | import dev.cosgy.textToSpeak.Bot 21 | import dev.cosgy.textToSpeak.commands.AdminCommand 22 | import net.dv8tion.jda.api.EmbedBuilder 23 | import org.slf4j.Logger 24 | import org.slf4j.LoggerFactory 25 | import java.awt.Color 26 | 27 | class GuildSettings(private val bot: Bot) : AdminCommand() { 28 | var log: Logger = LoggerFactory.getLogger(this.javaClass) 29 | 30 | init { 31 | name = "gsettings" 32 | help = "ギルドの現在の設定を確認できます。" 33 | } 34 | 35 | override fun execute(event: SlashCommandEvent) { 36 | if (!checkAdminPermission(event.client, event)) { 37 | event.reply(event.client.warning + "権限がないため実行できません。").queue() 38 | return 39 | } 40 | val settings = bot.settingsManager.getSettings(event.guild!!) 41 | var text = "null" 42 | if (settings.getTextChannel(event.guild) != null) { 43 | text = settings.getTextChannel(event.guild)!!.name 44 | } 45 | val builder = EmbedBuilder() 46 | .setColor(Color.orange) 47 | .setTitle(event.guild!!.name + "の設定") 48 | .addField("ユーザー名読み上げ:", if (settings.isReadName()) "有効" else "無効", false) 49 | .addField( 50 | "参加、退出時の読み上げ:", 51 | if (settings.isJoinAndLeaveRead()) "有効" else "無効", 52 | false 53 | ) 54 | .addField( 55 | "ニックネーム優先:", 56 | if (settings.isReadNic()) "有効" else "無効", 57 | false 58 | ) 59 | .addField("読み上げるチャンネル:", text, false) 60 | .addField("読み上げの主音量:", settings.volume.toString(), false) 61 | event.replyEmbeds(builder.build()).queue() 62 | } 63 | 64 | override fun execute(event: CommandEvent) { 65 | val settings = bot.settingsManager.getSettings(event.guild) 66 | var text = "null" 67 | if (settings.getTextChannel(event.guild) != null) { 68 | text = settings.getTextChannel(event.guild)!!.name 69 | } 70 | val builder = EmbedBuilder() 71 | .setColor(Color.orange) 72 | .setTitle(event.guild.name + "の設定") 73 | .addField("ユーザー名読み上げ:", if (settings.isReadName()) "有効" else "無効", false) 74 | .addField( 75 | "参加、退出時の読み上げ:", 76 | if (settings.isJoinAndLeaveRead()) "有効" else "無効", 77 | false 78 | ) 79 | .addField( 80 | "ニックネーム優先:", 81 | if (settings.isReadNic()) "有効" else "無効", 82 | false 83 | ) 84 | .addField("読み上げるチャンネル:", text, false) 85 | .addField("読み上げの主音量:", settings.volume.toString(), false) 86 | event.reply(builder.build()) 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/admin/JLReadCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.admin 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommandEvent 20 | import dev.cosgy.textToSpeak.Bot 21 | import dev.cosgy.textToSpeak.commands.AdminCommand 22 | import net.dv8tion.jda.api.interactions.commands.OptionType 23 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 24 | import org.slf4j.Logger 25 | import org.slf4j.LoggerFactory 26 | 27 | class JLReadCmd(private val bot: Bot) : AdminCommand() { 28 | var log: Logger = LoggerFactory.getLogger(this.javaClass) 29 | 30 | init { 31 | name = "jlread" 32 | help = "ボイスチャンネルにユーザーが参加または退出した時にユーザー名を読み上げるか否かを設定します。" 33 | 34 | options = listOf(OptionData(OptionType.BOOLEAN, "value", "機能を有効にするか否か", false)) 35 | } 36 | 37 | override fun execute(event: SlashCommandEvent) { 38 | if (!checkAdminPermission(event.client, event)) { 39 | event.reply(event.client.warning + "権限がないため実行できません。").queue() 40 | return 41 | } 42 | val settings = bot.settingsManager.getSettings(event.guild!!) 43 | 44 | if (event.getOption("value") == null) { 45 | settings.setJoinAndLeaveRead(!settings.isJoinAndLeaveRead()) 46 | event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを${if (settings.isJoinAndLeaveRead()) "有効" else "無効"}にしました。") 47 | .queue() 48 | } else { 49 | val args = event.getOption("value")!!.asBoolean 50 | settings.setJoinAndLeaveRead(args) 51 | event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを${if (args) "有効" else "無効"}にしました。") 52 | .queue() 53 | } 54 | } 55 | 56 | override fun execute(event: CommandEvent) { 57 | val settings = bot.settingsManager.getSettings(event.guild) 58 | 59 | settings.setJoinAndLeaveRead(!settings.isJoinAndLeaveRead()) 60 | event.reply("ボイスチャンネルにユーザーが参加、退出した際の読み上げを${if (settings.isJoinAndLeaveRead()) "有効" else "無効"}にしました。") 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/admin/NicReadCmd.kt: -------------------------------------------------------------------------------- 1 | package dev.cosgy.textToSpeak.commands.admin 2 | 3 | import com.jagrosh.jdautilities.command.CommandEvent 4 | import com.jagrosh.jdautilities.command.SlashCommandEvent 5 | import dev.cosgy.textToSpeak.Bot 6 | import dev.cosgy.textToSpeak.commands.AdminCommand 7 | import net.dv8tion.jda.api.interactions.commands.OptionType 8 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 9 | import org.slf4j.Logger 10 | import org.slf4j.LoggerFactory 11 | 12 | class NicReadCmd(private val bot: Bot) : AdminCommand() { 13 | var log: Logger = LoggerFactory.getLogger(this.javaClass) 14 | 15 | init { 16 | name = "readnic" 17 | help = "ニックネーム読み上げを優先するかを設定します。。" 18 | 19 | options = listOf(OptionData(OptionType.BOOLEAN, "value", "ニックネームを優先するか", false)) 20 | } 21 | 22 | override fun execute(event: SlashCommandEvent) { 23 | if (!checkAdminPermission(event.client, event)) { 24 | event.reply("${event.client.warning}権限がないため実行できません。").queue() 25 | return 26 | } 27 | 28 | val settings = bot.settingsManager.getSettings(event.guild!!) 29 | 30 | if (event.getOption("value") == null) { 31 | settings.setReadNic(!settings.isReadNic()) 32 | event.reply("ニックネーム読み上げの優先を${if (settings.isReadNic()) "有効" else "無効"}にしました。") 33 | .queue() 34 | } else { 35 | val args = event.getOption("value")!!.asBoolean 36 | 37 | settings.setReadNic(args) 38 | 39 | event.reply("ニックネーム読み上げの優先を${if (args) "有効" else "無効"}にしました。").queue() 40 | } 41 | } 42 | 43 | override fun execute(event: CommandEvent) { 44 | val settings = bot.settingsManager.getSettings(event.guild) 45 | 46 | settings.setReadNic(!settings.isReadNic()) 47 | event.reply("ニックネーム読み上げの優先を${if (settings.isReadNic()) "有効" else "無効"}にしました。") 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/admin/SetReadNameCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.admin 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommandEvent 20 | import dev.cosgy.textToSpeak.Bot 21 | import dev.cosgy.textToSpeak.commands.AdminCommand 22 | import net.dv8tion.jda.api.interactions.commands.OptionType 23 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 24 | import org.slf4j.Logger 25 | import org.slf4j.LoggerFactory 26 | 27 | class SetReadNameCmd(private val bot: Bot) : AdminCommand() { 28 | var log: Logger = LoggerFactory.getLogger(this.javaClass) 29 | 30 | init { 31 | name = "setreadname" 32 | help = "テキストを読み上げる際にユーザー名も読み上げるかを設定します。" 33 | 34 | options = listOf(OptionData(OptionType.BOOLEAN, "value", "機能を有効にするか否か", false)) 35 | } 36 | 37 | override fun execute(event: SlashCommandEvent) { 38 | if (!checkAdminPermission(event.client, event)) { 39 | event.reply("${event.client.warning}権限がないため実行できません。").queue() 40 | return 41 | } 42 | 43 | val settings = bot.settingsManager.getSettings(event.guild!!) 44 | 45 | if (event.getOption("value") == null) { 46 | settings.setReadName(!settings.isReadName()) 47 | event.reply("ユーザー名の読み上げを${if (settings.isReadName()) "有効" else "無効"}にしました。").queue() 48 | } else { 49 | val args = event.getOption("value")!!.asBoolean 50 | 51 | settings.setReadName(args) 52 | 53 | event.reply("ユーザー名の読み上げを${if (args) "有効" else "無効"}にしました。").queue() 54 | } 55 | } 56 | 57 | override fun execute(event: CommandEvent) { 58 | val settings = bot.settingsManager.getSettings(event.guild) 59 | 60 | settings.setReadName(!settings.isReadName()) 61 | event.reply("ユーザー名の読み上げを${if (settings.isReadName()) "有効" else "無効"}にしました。") 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/admin/SettcCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.admin 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import com.jagrosh.jdautilities.commons.utils.FinderUtil 22 | import dev.cosgy.textToSpeak.Bot 23 | import dev.cosgy.textToSpeak.commands.AdminCommand 24 | import dev.cosgy.textToSpeak.settings.Settings 25 | import dev.cosgy.textToSpeak.utils.FormatUtil 26 | import net.dv8tion.jda.api.entities.channel.ChannelType 27 | import net.dv8tion.jda.api.interactions.commands.OptionType 28 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 29 | import org.slf4j.Logger 30 | import org.slf4j.LoggerFactory 31 | import java.util.* 32 | 33 | class SettcCmd(bot: Bot?) : AdminCommand() { 34 | var log: Logger = LoggerFactory.getLogger(this.javaClass) 35 | 36 | init { 37 | name = "settc" 38 | help = 39 | "読み上げをするチャンネルを設定します。読み上げするチャンネルを設定していない場合は、joinコマンドを最後に実行したチャンネルが読み上げ対象になります。" 40 | arguments = "<チャンネル名|NONE|なし>" 41 | children = arrayOf(Set(), None()) 42 | } 43 | 44 | override fun execute(event: SlashCommandEvent) {} 45 | override fun execute(event: CommandEvent) { 46 | if (event.args.isEmpty()) { 47 | event.reply("${event.client.error}チャンネルまたはNONEを含めてください。") 48 | return 49 | } 50 | val s = event.client.getSettingsFor(event.guild) 51 | if (event.args.lowercase(Locale.getDefault()).matches("(none|なし)".toRegex())) { 52 | s.setTextChannel(null) 53 | event.reply("${event.client.success}読み上げをするチャンネルの設定を無効にしました。") 54 | } else { 55 | val list = FinderUtil.findTextChannels(event.args, event.guild) 56 | if (list.isEmpty()) event.reply("${event.client.warning}一致するチャンネルが見つかりませんでした ${event.args}") else if (list.size > 1) event.reply( 57 | event.client.warning + FormatUtil.listOfTChannels(list, event.args) 58 | ) else { 59 | s.setTextChannel(list[0]) 60 | log.info("読み上げを行うチャンネルを設定しました。") 61 | event.reply("${event.client.success}読み上げるチャンネルを<#${list[0].id}>に設定しました。") 62 | } 63 | } 64 | } 65 | 66 | private class Set : AdminCommand() { 67 | init { 68 | name = "set" 69 | help = "読み上げるチャンネルを設定" 70 | val options: MutableList = ArrayList() 71 | options.add(OptionData(OptionType.CHANNEL, "channel", "テキストチャンネル", true)) 72 | this.options = options 73 | } 74 | 75 | override fun execute(event: SlashCommandEvent) { 76 | val s = event.client.getSettingsFor(event.guild) 77 | if (event.getOption("channel")!!.channelType != ChannelType.TEXT) { 78 | event.reply("${event.client.error}テキストチャンネルを設定して下さい。").queue() 79 | return 80 | } 81 | val channelId = event.getOption("channel")!!.asLong 82 | val tc = event.guild!!.getTextChannelById(channelId) 83 | s.setTextChannel(tc) 84 | event.reply("${event.client.success}読み上げるチャンネルを<#${tc!!.id}>に設定しました。").queue() 85 | } 86 | } 87 | 88 | private class None : AdminCommand() { 89 | init { 90 | name = "none" 91 | help = "読み上げるチャンネル設定をリセットします。" 92 | } 93 | 94 | override fun execute(event: SlashCommandEvent) { 95 | if (!checkAdminPermission(event.client, event)) { 96 | event.reply("${event.client.warning}権限がないため実行できません。").queue() 97 | return 98 | } 99 | val s = event.client.getSettingsFor(event.guild) 100 | s.setTextChannel(null) 101 | event.reply("${event.client.success}読み上げるチャンネル設定をリセットしました。").queue() 102 | } 103 | 104 | override fun execute(event: CommandEvent) { 105 | val s = event.client.getSettingsFor(event.guild) 106 | s.setTextChannel(null) 107 | event.reply("${event.client.success}読み上げるチャンネル設定をリセットしました。") 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/dictionary/AddWordCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.dictionary 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import com.jagrosh.jdautilities.menu.ButtonMenu 22 | import dev.cosgy.textToSpeak.Bot 23 | import net.dv8tion.jda.api.EmbedBuilder 24 | import net.dv8tion.jda.api.entities.Message 25 | import net.dv8tion.jda.api.entities.emoji.Emoji 26 | import net.dv8tion.jda.api.exceptions.PermissionException 27 | import net.dv8tion.jda.api.interactions.commands.OptionType 28 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 29 | import java.awt.Color 30 | import java.util.concurrent.TimeUnit 31 | import java.util.regex.Pattern 32 | 33 | class AddWordCmd(private val bot: Bot) : SlashCommand() { 34 | init { 35 | name = "wdad" 36 | help = "辞書に単語を追加します。辞書に単語が存在している場合は上書きされます。" 37 | this.category = Category("辞書") 38 | val options: MutableList = ArrayList() 39 | options.add(OptionData(OptionType.STRING, "word", "単語", true)) 40 | options.add(OptionData(OptionType.STRING, "reading", "読み方(カタカナ)", true)) 41 | this.options = options 42 | } 43 | 44 | private fun handleCommand(event: SlashCommandEvent, word: String, reading: String) { 45 | val guildId = event.guild!!.idLong 46 | val dictionary = bot.dictionary 47 | val isWordExist = dictionary!!.getWords(guildId).containsKey(word) 48 | event.deferReply().queue() 49 | if (isWordExist) { 50 | val no = "❌" 51 | val ok = "✔" 52 | 53 | ButtonMenu.Builder() 54 | .setText("単語が既に存在します。上書きしますか?") 55 | .addChoices(no, ok) 56 | .setEventWaiter(bot.waiter) 57 | .setTimeout(30, TimeUnit.SECONDS) 58 | .setAction { re: Emoji -> 59 | if (re.name == ok) { 60 | dictionary.updateDictionary(guildId, word, reading) 61 | sendSuccessMessage(event) 62 | } else { 63 | 64 | event.hook.sendMessage("辞書登録をキャンセルしました。").queue() 65 | } 66 | }.setFinalAction { m: Message -> 67 | try { 68 | m.clearReactions().queue() 69 | m.delete().queue() 70 | } catch (ignore: PermissionException) { 71 | } 72 | }.build().display(event.messageChannel) 73 | } else { 74 | dictionary.updateDictionary(guildId, word, reading) 75 | sendSuccessMessage(event) 76 | } 77 | } 78 | 79 | private fun sendSuccessMessage(event: SlashCommandEvent) { 80 | val word = event.getOption("word")!!.asString 81 | val reading = event.getOption("reading")!!.asString 82 | val builder = EmbedBuilder() 83 | .setColor(SUCCESS_COLOR) 84 | .setTitle("単語を追加しました。") 85 | .addField("単語", "```${replaceEmoji(word)}```", false) 86 | .addField("読み", "```${reading}```", false) 87 | event.hook.sendMessageEmbeds(builder.build()).queue() 88 | } 89 | 90 | override fun execute(event: SlashCommandEvent) { 91 | val word = event.getOption("word")!!.asString 92 | val reading = event.getOption("reading")!!.asString 93 | if (!isKatakana(reading)) { 94 | event.reply("読み方はすべてカタカナで入力して下さい。").setEphemeral(true).queue() 95 | return 96 | } 97 | handleCommand(event, replaceEmoji(word), reading) 98 | } 99 | 100 | /*** 101 | * テキストコマンド用 102 | */ 103 | private fun handleCommand(event: CommandEvent, word: String, reading: String) { 104 | val guildId = event.guild!!.idLong 105 | val dictionary = bot.dictionary 106 | val isWordExist = dictionary!!.getWords(guildId).containsKey(word) 107 | if (isWordExist) { 108 | val no = "❌" 109 | val ok = "✔" 110 | 111 | ButtonMenu.Builder() 112 | .setText("単語が既に存在します。上書きしますか?") 113 | .addChoices(no, ok) 114 | .setEventWaiter(bot.waiter) 115 | .setTimeout(30, TimeUnit.SECONDS) 116 | .setAction { re: Emoji -> 117 | if (re.name == ok) { 118 | dictionary.updateDictionary(guildId, word, reading) 119 | sendSuccessMessage(event) 120 | } else { 121 | 122 | event.reply("辞書登録をキャンセルしました。") 123 | } 124 | }.setFinalAction { m: Message -> 125 | try { 126 | m.clearReactions().queue() 127 | m.delete().queue() 128 | } catch (ignore: PermissionException) { 129 | } 130 | }.build().display(event.channel) 131 | } else { 132 | dictionary.updateDictionary(guildId, word, reading) 133 | sendSuccessMessage(event) 134 | } 135 | } 136 | 137 | private fun sendSuccessMessage(event: CommandEvent) { 138 | val args = event.args.split("\\s+".toRegex(), 2).toTypedArray() 139 | val word = args[0] 140 | val reading = args[1] 141 | val builder = EmbedBuilder() 142 | .setColor(SUCCESS_COLOR) 143 | .setTitle("単語を追加しました。") 144 | .addField("単語", "```${replaceEmoji(word)}```", false) 145 | .addField("読み", "```${reading}```", false) 146 | event.reply(builder.build()) 147 | } 148 | 149 | override fun execute(event: CommandEvent) { 150 | val args = event.args.split("\\s+".toRegex(), 2).toTypedArray() 151 | if (args.size < 2) { 152 | event.reply("コマンドが無効です。単語と読み方の2つを入力して実行して下さい。") 153 | return 154 | } 155 | val word = args[0] 156 | val reading = args[1] 157 | if (!isKatakana(reading)) { 158 | event.reply("読み方はすべてカタカナで入力して下さい。") 159 | return 160 | } 161 | handleCommand(event, replaceEmoji(word), reading) 162 | } 163 | 164 | companion object { 165 | private val SUCCESS_COLOR = Color(0, 163, 129) 166 | 167 | //private val ERROR_COLOR = Color.RED 168 | //private const val INVALID_ARGS_MESSAGE = "コマンドが無効です。単語と読み方の2つを入力して実行して下さい。" 169 | //private const val USAGE_MESSAGE = "使用方法: /addword <単語> <読み方>" 170 | private const val KATAKANA_REGEX = "^[ァ-ヶー]*$" 171 | private fun isKatakana(str: String): Boolean { 172 | return Pattern.matches(KATAKANA_REGEX, str) 173 | } 174 | 175 | private val EMOJI_REGEX = """<(:[a-z0-9_]+:)\d+>""".toRegex() 176 | private fun replaceEmoji(str: String): String { 177 | return EMOJI_REGEX.replace(str, "$1") 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/dictionary/DlWordCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.dictionary 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.EmbedBuilder 23 | import net.dv8tion.jda.api.interactions.commands.OptionType 24 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 25 | 26 | class DlWordCmd(private val bot: Bot) : SlashCommand() { 27 | 28 | init { 29 | name = "wddl" 30 | help = "辞書に登録されている単語を削除します。" 31 | this.category = Category("辞書") 32 | val options: MutableList = ArrayList() 33 | options.add(OptionData(OptionType.STRING, "word", "単語", true)) 34 | this.options = options 35 | } 36 | 37 | override fun execute(event: SlashCommandEvent) { 38 | val words = bot.dictionary?.getWords(event.guild!!.idLong) 39 | val args = event.getOption("word")!!.asString 40 | if (!words!!.containsKey(args)) { 41 | event.reply("${args}は、辞書に登録されていません。").queue() 42 | return 43 | } 44 | val result = bot.dictionary?.deleteDictionary(event.guild!!.idLong, args) 45 | if (result == true) { 46 | event.reply("単語(${args})を削除しました。").queue() 47 | } else { 48 | event.reply("削除中に問題が発生しました。").setEphemeral(true).queue() 49 | } 50 | } 51 | 52 | override fun execute(event: CommandEvent) { 53 | if (event.args.isEmpty() && event.message.attachments.isEmpty()) { 54 | val builder = EmbedBuilder() 55 | .setTitle("dlwordコマンド") 56 | .addField("使用方法:", "$name <単語>", false) 57 | .addField("説明:", help, false) 58 | event.reply(builder.build()) 59 | return 60 | } 61 | val words = bot.dictionary?.getWords(event.guild.idLong) 62 | val args = event.args 63 | if (!words!!.containsKey(args)) { 64 | event.reply("${args}は、辞書に登録されていません。") 65 | return 66 | } 67 | val result = bot.dictionary?.deleteDictionary(event.guild.idLong, args) 68 | if (result == true) { 69 | event.reply("単語(${args})を削除しました。") 70 | } else { 71 | event.reply("削除中に問題が発生しました。") 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/dictionary/WordListCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.dictionary 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import com.jagrosh.jdautilities.menu.Paginator 22 | import dev.cosgy.textToSpeak.Bot 23 | import net.dv8tion.jda.api.Permission 24 | import net.dv8tion.jda.api.entities.Message 25 | import net.dv8tion.jda.api.exceptions.PermissionException 26 | import net.dv8tion.jda.api.interactions.InteractionHook 27 | import java.util.concurrent.TimeUnit 28 | import java.util.stream.Collectors 29 | 30 | class WordListCmd(private val bot: Bot) : SlashCommand() { 31 | private val builder: Paginator.Builder 32 | 33 | init { 34 | name = "wdls" 35 | help = "辞書に登録してある単語をリストアップします。" 36 | this.category = Category("辞書") 37 | botPermissions = arrayOf(Permission.MESSAGE_ADD_REACTION, Permission.MESSAGE_EMBED_LINKS) 38 | builder = Paginator.Builder() 39 | .setColumns(1) 40 | .setFinalAction { m: Message -> 41 | try { 42 | m.clearReactions().queue() 43 | } catch (ignore: PermissionException) { 44 | } 45 | } 46 | .setItemsPerPage(10) 47 | .waitOnSinglePage(false) 48 | .useNumberedItems(true) 49 | .showPageNumbers(true) 50 | .wrapPageEnds(true) 51 | .setEventWaiter(bot.waiter) 52 | .setTimeout(1, TimeUnit.MINUTES) 53 | } 54 | 55 | override fun execute(event: SlashCommandEvent) { 56 | event.reply("単語一覧を表示します。").queue { m: InteractionHook -> 57 | val wordList = bot.dictionary?.getWords(event.guild!!.idLong) 58 | ?.entries?.stream() 59 | ?.map { (key, value): Map.Entry -> "$key-$value" } 60 | ?.collect(Collectors.toList()) 61 | if (wordList != null) { 62 | if (wordList.isEmpty()) { 63 | m.editOriginal("単語が登録されていません。").queue() 64 | return@queue 65 | } 66 | } 67 | m.deleteOriginal().queue() 68 | builder.setText("単語一覧") 69 | .setItems(*wordList!!.toTypedArray()) 70 | .setUsers(event.user) 71 | .setColor(event.guild!!.selfMember.color) 72 | builder.build().paginate(event.channel, 1) 73 | } 74 | } 75 | 76 | override fun execute(event: CommandEvent) { 77 | var pagenum = 1 78 | try { 79 | pagenum = event.args.toInt() 80 | } catch (ignore: NumberFormatException) { 81 | } 82 | val wordList = bot.dictionary?.getWords(event.guild.idLong) 83 | ?.entries?.stream() 84 | ?.map { (key, value): Map.Entry -> "$key-$value" } 85 | ?.collect(Collectors.toList()) 86 | if (wordList != null) { 87 | if (wordList.isEmpty()) { 88 | event.reply("単語が登録されていません。") 89 | return 90 | } 91 | } 92 | builder.setText("単語一覧") 93 | .setItems(*wordList!!.toTypedArray()) 94 | .setUsers(event.author) 95 | .setColor(event.selfMember.color) 96 | builder.build().paginate(event.channel, pagenum) 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/AboutCommand.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandClient 19 | import com.jagrosh.jdautilities.command.CommandEvent 20 | import com.jagrosh.jdautilities.command.SlashCommand 21 | import com.jagrosh.jdautilities.command.SlashCommandEvent 22 | import com.jagrosh.jdautilities.commons.JDAUtilitiesInfo 23 | import com.jagrosh.jdautilities.doc.standard.CommandInfo 24 | import net.dv8tion.jda.api.EmbedBuilder 25 | import net.dv8tion.jda.api.JDA 26 | import net.dv8tion.jda.api.JDAInfo 27 | import net.dv8tion.jda.api.Permission 28 | import net.dv8tion.jda.api.entities.Guild 29 | import net.dv8tion.jda.api.entities.channel.ChannelType 30 | import org.slf4j.LoggerFactory 31 | import java.awt.Color 32 | import java.util.* 33 | 34 | /** 35 | * @author Kosugi_kun 36 | */ 37 | @CommandInfo(name = ["About"], description = "ボットに関する情報を表示します") 38 | class AboutCommand(private val color: Color, private val description: String, vararg perms: Permission) : 39 | SlashCommand() { 40 | private val perms: Array 41 | private var isAuthor = true 42 | private var replacementIcon = "+" 43 | private var oauthLink: String? = null 44 | 45 | init { 46 | name = "about" 47 | help = "ボットに関する情報を表示します" 48 | guildOnly = false 49 | this.perms = perms 50 | botPermissions = arrayOf(Permission.MESSAGE_EMBED_LINKS) 51 | } 52 | 53 | fun setIsAuthor(value: Boolean) { 54 | isAuthor = value 55 | } 56 | 57 | fun setReplacementCharacter(value: String) { 58 | replacementIcon = value 59 | } 60 | 61 | override fun execute(event: SlashCommandEvent) { 62 | getOauthLink(event.jda) 63 | val builder = EmbedBuilder() 64 | builder.setColor(if (event.isFromType(ChannelType.TEXT)) event.guild!!.selfMember.color else color) 65 | builder.setAuthor(event.jda.selfUser.name + "について!", null, event.jda.selfUser.avatarUrl) 66 | val cosgyOwner = "Cosgy Devが運営、開発をしています。" 67 | val author = 68 | if (event.jda.getUserById(event.client.ownerId) == null) "<@" + event.client.ownerId + ">" else Objects.requireNonNull( 69 | event.jda.getUserById(event.client.ownerId) 70 | )?.name 71 | val descr = StringBuilder().append("こんにちは! **").append(event.jda.selfUser.name).append("**です。 ") 72 | .append(description).append("は、") 73 | .append(JDAUtilitiesInfo.AUTHOR + "の[コマンド拡張](" + JDAUtilitiesInfo.GITHUB + ") (") 74 | .append(JDAUtilitiesInfo.VERSION).append(")と[JDAライブラリ](https://github.com/DV8FromTheWorld/JDA) (") 75 | .append(JDAInfo.VERSION).append(")を使用しており、") 76 | .append(if (isAuthor) cosgyOwner else author + "が所有しています。") 77 | .append(event.jda.selfUser.name) 78 | .append("についての質問などは[Cosgy Dev公式チャンネル](https://discord.gg/RBpkHxf)へお願いします。") 79 | .append("\nこのボットの使用方法は`").append("/" + event.client.helpWord) 80 | .append("`で確認することができます。") 81 | getMessage(builder, descr, event.jda, event.client) 82 | event.replyEmbeds(builder.build()).queue() 83 | } 84 | 85 | private fun getMessage(builder: EmbedBuilder, descr: StringBuilder, jda: JDA, client: CommandClient) { 86 | builder.setDescription(descr) 87 | if (jda.shardInfo.shardTotal == 1) { 88 | builder.addField( 89 | "ステータス", """${jda.guilds.size} サーバー 90 | |1シャード""".trimMargin(), true 91 | ) 92 | builder.addField( 93 | "ユーザー", """${jda.users.size} ユニーク 94 | |${jda.guilds.stream().mapToInt { g: Guild -> g.members.size }.sum()} 合計""".trimMargin(), true 95 | ) 96 | builder.addField( 97 | "チャンネル", """${jda.textChannels.size} テキスト 98 | |${jda.voiceChannels.size} ボイス""".trimMargin(), true 99 | ) 100 | } else { 101 | builder.addField( 102 | "ステータス", """${client.totalGuilds} サーバー 103 | |シャード ${jda.shardInfo.shardId + 1}/${jda.shardInfo.shardTotal}""".trimMargin(), true 104 | ) 105 | builder.addField( 106 | "", """${jda.users.size} ユーザーのシャード 107 | |${jda.guilds.size} サーバー""".trimMargin(), true 108 | ) 109 | builder.addField( 110 | "", """${jda.textChannels.size} テキストチャンネル 111 | |${jda.voiceChannels.size} ボイスチャンネル""".trimMargin(), true 112 | ) 113 | } 114 | builder.setFooter("再起動が行われた時間") 115 | builder.setTimestamp(client.startTime) 116 | } 117 | 118 | override fun execute(event: CommandEvent) { 119 | getOauthLink(event.jda) 120 | val builder = EmbedBuilder() 121 | builder.setColor(if (event.isFromType(ChannelType.TEXT)) event.guild.selfMember.color else color) 122 | builder.setAuthor(event.selfUser.name + "について!", null, event.selfUser.avatarUrl) 123 | val cosgyOwner = "Cosgy Devが運営、開発をしています。" 124 | val author = 125 | if (event.jda.getUserById(event.client.ownerId) == null) "<@" + event.client.ownerId + ">" else Objects.requireNonNull( 126 | event.jda.getUserById(event.client.ownerId) 127 | )?.name 128 | val descr = StringBuilder().append("こんにちは! **").append(event.selfUser.name).append("**です。 ") 129 | .append(description).append("は、") 130 | .append(JDAUtilitiesInfo.AUTHOR + "の[コマンド拡張](" + JDAUtilitiesInfo.GITHUB + ") (") 131 | .append(JDAUtilitiesInfo.VERSION).append(")と[JDAライブラリ](https://github.com/DV8FromTheWorld/JDA) (") 132 | .append(JDAInfo.VERSION).append(")を使用しており、") 133 | .append(if (isAuthor) cosgyOwner else author + "が所有しています。") 134 | .append(event.selfUser.name) 135 | .append("についての質問などは[Cosgy Dev公式チャンネル](https://discord.gg/RBpkHxf)へお願いします。") 136 | .append("\nこのボットの使用方法は`").append("/" + event.client.helpWord) 137 | .append("`で確認することができます。") 138 | getMessage(builder, descr, event.jda, event.client) 139 | event.reply(builder.build()) 140 | } 141 | 142 | private fun getOauthLink(jda: JDA) { 143 | if (oauthLink == null) { 144 | oauthLink = try { 145 | val info = jda.retrieveApplicationInfo().complete() 146 | if (info.isBotPublic) info.getInviteUrl(0L, *perms) else "" 147 | } catch (e: Exception) { 148 | val log = LoggerFactory.getLogger("OAuth2") 149 | log.error("招待リンクを生成できませんでした ", e) 150 | "" 151 | } 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/ByeCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import dev.cosgy.textToSpeak.audio.AudioHandler 23 | import net.dv8tion.jda.api.EmbedBuilder 24 | import java.awt.Color 25 | import java.io.IOException 26 | 27 | class ByeCmd(private val bot: Bot) : SlashCommand() { 28 | init { 29 | name = "bye" 30 | help = "ボイスチャンネルから退出します。" 31 | guildOnly = true 32 | } 33 | 34 | override fun execute(event: SlashCommandEvent) { 35 | val handler = event.guild!!.audioManager.sendingHandler as AudioHandler? 36 | 37 | if (handler == null) { 38 | event.reply("ボイスチャンネルに接続していません。").setEphemeral(true).queue() 39 | return 40 | } 41 | 42 | handler.stopAndClear() 43 | try { 44 | bot.voiceCreation.clearGuildFolder(event.guild!!) 45 | } catch (e: IOException) { 46 | throw RuntimeException(e) 47 | } 48 | event.guild!!.audioManager.closeAudioConnection() 49 | val builder = EmbedBuilder() 50 | builder.setColor(Color(180, 76, 151)) 51 | builder.setTitle("VCから切断") 52 | builder.setDescription("ボイスチャンネルから切断しました。") 53 | event.replyEmbeds(builder.build()).queue() 54 | } 55 | 56 | override fun execute(event: CommandEvent) { 57 | val handler = event.guild.audioManager.sendingHandler as AudioHandler? 58 | handler!!.stopAndClear() 59 | try { 60 | bot.voiceCreation.clearGuildFolder(event.guild) 61 | } catch (e: IOException) { 62 | throw RuntimeException(e) 63 | } 64 | event.guild.audioManager.closeAudioConnection() 65 | val builder = EmbedBuilder() 66 | builder.setColor(Color(180, 76, 151)) 67 | builder.setTitle("VCから切断") 68 | builder.setDescription("ボイスチャンネルから切断しました。") 69 | event.reply(builder.build()) 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/HelpCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import dev.cosgy.textToSpeak.audio.VoiceCreation 23 | import net.dv8tion.jda.api.EmbedBuilder 24 | import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel 25 | import org.slf4j.LoggerFactory 26 | import java.awt.Color 27 | import java.util.* 28 | 29 | class HelpCmd(var bot: Bot) : SlashCommand() { 30 | init { 31 | name = "help" 32 | help = "コマンド一覧を表示します。" 33 | } 34 | 35 | override fun execute(event: SlashCommandEvent) { 36 | val eBuilder = EmbedBuilder() 37 | eBuilder.setTitle("**" + event.jda.selfUser.name + "** コマンド一覧") 38 | eBuilder.setColor(Color(245, 229, 107)) 39 | val builder = StringBuilder() 40 | var category: Category? = null 41 | val commands = event.client.slashCommands 42 | for (command in commands) { 43 | if (!command.isHidden && (!command.isOwnerCommand || event.member!!.isOwner)) { 44 | if (category != command.category) { 45 | category = command.category 46 | builder.append("\n\n __").append(if (category == null) "カテゴリなし" else category.name) 47 | .append("__:\n") 48 | } 49 | builder.append("\n`").append("/").append(command.name) 50 | .append(if (command.arguments == null) "`" else " " + command.arguments + "`") 51 | .append(" - ").append(command.help) 52 | } 53 | } 54 | if (event.client.serverInvite != null) builder.append("\n\nさらにヘルプが必要な場合は、公式サーバーに参加することもできます: ") 55 | .append(event.client.serverInvite) 56 | eBuilder.setDescription(builder) 57 | if (bot.config.helpToDm) { 58 | event.user.openPrivateChannel() 59 | .flatMap { channel: PrivateChannel -> channel.sendMessageEmbeds(eBuilder.build()) }.queue() 60 | } else { 61 | event.replyEmbeds(eBuilder.build()).queue() 62 | } 63 | } 64 | 65 | public override fun execute(event: CommandEvent) { 66 | val eBuilder = EmbedBuilder() 67 | eBuilder.setTitle("**" + event.jda.selfUser.name + "** コマンド一覧") 68 | eBuilder.setColor(Color(245, 229, 107)) 69 | val builder = StringBuilder() 70 | var category: Category? = null 71 | val commands = event.client.commands 72 | for (command in commands) { 73 | if (!command.isHidden && (!command.isOwnerCommand || event.isOwner)) { 74 | if (category != command.category) { 75 | category = command.category 76 | builder.append("\n\n __").append(if (category == null) "カテゴリなし" else category.name) 77 | .append("__:\n") 78 | } 79 | builder.append("\n`").append("/").append(command.name) 80 | .append(if (command.arguments == null) "`" else " " + command.arguments + "`") 81 | .append(" - ").append(command.help) 82 | } 83 | } 84 | if (event.client.serverInvite != null) builder.append("\n\nさらにヘルプが必要な場合は、公式サーバーに参加することもできます: ") 85 | .append(event.client.serverInvite) 86 | eBuilder.setDescription(builder) 87 | if (bot.config.helpToDm) { 88 | event.author.openPrivateChannel() 89 | .flatMap { channel: PrivateChannel -> channel.sendMessageEmbeds(eBuilder.build()) }.queue() 90 | } else { 91 | event.reply(eBuilder.build()) 92 | } 93 | } 94 | 95 | companion object { 96 | private val logger = LoggerFactory.getLogger(VoiceCreation::class.java) 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/JoinCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import dev.cosgy.textToSpeak.settings.Settings 23 | import dev.cosgy.textToSpeak.utils.ReadChannel 24 | import net.dv8tion.jda.api.EmbedBuilder 25 | import net.dv8tion.jda.api.exceptions.PermissionException 26 | import java.awt.Color 27 | 28 | class JoinCmd(private var bot: Bot) : SlashCommand() { 29 | init { 30 | name = "join" 31 | help = "ボイスチャンネルに参加します。" 32 | guildOnly = true 33 | } 34 | 35 | override fun execute(event: SlashCommandEvent) { 36 | event.deferReply().queue() 37 | val settings = event.client.getSettingsFor(event.guild) 38 | val channel = settings.getTextChannel(event.guild) 39 | // VoiceChannel voiceChannel = settings.getVoiceChannel(event.getGuild()); 40 | bot.playerManager.setUpHandler(event.guild!!) 41 | val userState = event.member!!.voiceState 42 | val builder = EmbedBuilder() 43 | builder.setColor(Color(76, 108, 179)) 44 | builder.setTitle("VCに接続") 45 | if (!userState!!.inAudioChannel() || userState.isDeafened) { 46 | builder.setDescription( 47 | String.format( 48 | "このコマンドを使用するには、%sに参加している必要があります。", 49 | "音声チャンネル" 50 | ) 51 | ) 52 | event.replyEmbeds(builder.build()).queue() 53 | return 54 | } 55 | if (channel == null) { 56 | builder.addField("読み上げ対象", event.channel.name, true) 57 | } else { 58 | builder.addField("読み上げ対象", channel.name, true) 59 | } 60 | try { 61 | // ボイスチャンネル接続完了のメッセージに現在の設定を表示 62 | event.guild!!.audioManager.openAudioConnection(userState.channel) 63 | builder.addField("ボイスチャンネル", String.format("**%s**", userState.channel!!.name), false) 64 | builder.setDescription("ボイスチャンネルへの接続に成功しました。") 65 | builder.addField( 66 | "設定", 67 | "ユーザー名読み上げ:${if (settings.isReadName()) "有効" else "無効"}\n" + 68 | "参加、退出読み上げ:${if (settings.isJoinAndLeaveRead()) "有効" else "無効"}\n" + 69 | "ニックネーム優先:${if (settings.isReadNic()) "有効" else "無効"}", true 70 | ) 71 | event.hook.sendMessageEmbeds(builder.build()).queue() 72 | ReadChannel.setChannel(event.guild!!.idLong, event.textChannel.idLong) 73 | } catch (ex: PermissionException) { 74 | builder.appendDescription( 75 | event.client.error + String.format( 76 | "**%s**に接続できません!", 77 | userState.channel!!.name 78 | ) 79 | ) 80 | builder.addField( 81 | "ボイスチャンネル", event.client.error + String.format( 82 | "**%s**に接続できません!", 83 | userState.channel!!.name 84 | ), false 85 | ) 86 | event.hook.sendMessageEmbeds(builder.build()).queue() 87 | } 88 | } 89 | 90 | override fun execute(event: CommandEvent) { 91 | val settings = event.client.getSettingsFor(event.guild) 92 | val channel = settings.getTextChannel(event.guild) 93 | bot.playerManager.setUpHandler(event.guild) 94 | val userState = event.member.voiceState 95 | val builder = EmbedBuilder() 96 | builder.setColor(Color(76, 108, 179)) 97 | builder.setTitle("VCに接続") 98 | if (!userState!!.inAudioChannel()) { 99 | builder.setDescription("このコマンドを使用するには、ボイスチャンネルに参加している必要があります。") 100 | event.reply(builder.build()) 101 | return 102 | } 103 | if (channel == null) { 104 | builder.addField("読み上げ対象", event.channel.name, true) 105 | } else { 106 | builder.addField("読み上げ対象", channel.name, true) 107 | } 108 | try { 109 | event.guild.audioManager.openAudioConnection(userState.channel) 110 | builder.addField("ボイスチャンネル", String.format("**%s**", userState.channel!!.name), false) 111 | builder.setDescription("ボイスチャンネルへの接続に成功しました。") 112 | event.reply(builder.build()) 113 | ReadChannel.setChannel(event.guild.idLong, event.textChannel.idLong) 114 | } catch (ex: PermissionException) { 115 | builder.setDescription("ボイスチャンネルへの接続に失敗しました。") 116 | event.reply(builder.build()) 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/SetIntonationCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.EmbedBuilder 23 | import net.dv8tion.jda.api.interactions.commands.OptionType 24 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 25 | import java.math.BigDecimal 26 | 27 | class SetIntonationCmd(private val bot: Bot) : SlashCommand() { 28 | init { 29 | name = "setinto" 30 | help = "F0系列内変動の重みの設定を変更します。" 31 | guildOnly = false 32 | category = Category("設定") 33 | options = listOf(OptionData(OptionType.STRING, "value", "0.1~100.0", true)) 34 | } 35 | 36 | override fun execute(event: SlashCommandEvent) { 37 | val bd = event.getOption("value")?.asString?.toBigDecimalOrNull() 38 | 39 | if (bd == null || bd < BigDecimal.ZERO || bd > BigDecimal("100.0")) { 40 | event.reply("有効な数値を設定してください。0.1~100.0").queue() 41 | return 42 | } 43 | 44 | val settings = bot.userSettingsManager.getSettings(event.user.idLong) 45 | settings.intonationSetting = bd.toFloat() 46 | event.reply("F0系列内変動の重みを${bd}に設定しました。").queue() 47 | } 48 | 49 | override fun execute(event: CommandEvent) { 50 | if (event.args.isEmpty()) { 51 | val builder = EmbedBuilder() 52 | .setTitle("setintoコマンド") 53 | .addField("使用方法:", "$name <数値(0.0~)>", false) 54 | .addField( 55 | "説明:", 56 | "F0系列内変動の重みを変更します。F0系列内変動の重みは、0.0以上の数値で設定して下さい。", 57 | false 58 | ) 59 | event.reply(builder.build()) 60 | return 61 | } 62 | 63 | val bd = event.args.toBigDecimalOrNull() 64 | 65 | if (bd == null || bd < BigDecimal.ZERO || bd > BigDecimal("100.0")) { 66 | event.reply("有効な数値を設定してください。0.1~100.0") 67 | return 68 | } 69 | 70 | val settings = bot.userSettingsManager.getSettings(event.author.idLong) 71 | settings.intonationSetting = bd.toFloat() 72 | event.reply("F0系列内変動の重みを${bd}に設定しました。") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/SetSpeedCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.EmbedBuilder 23 | import net.dv8tion.jda.api.interactions.commands.OptionType 24 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 25 | import java.math.BigDecimal 26 | 27 | class SetSpeedCmd(private val bot: Bot) : SlashCommand() { 28 | init { 29 | name = "setspeed" 30 | help = "読み上げ速度の設定を変更します。" 31 | guildOnly = false 32 | category = Category("設定") 33 | options = mutableListOf(OptionData(OptionType.STRING, "value", "0.1~100.0", true)) 34 | } 35 | 36 | override fun execute(event: SlashCommandEvent) { 37 | val args = event.getOption("value")?.asString 38 | val bd = try { 39 | BigDecimal(args) 40 | } catch (e: NumberFormatException) { 41 | event.reply("数値を設定して下さい。").queue() 42 | return 43 | } 44 | val min = BigDecimal.ZERO 45 | val max = BigDecimal("100.0") 46 | if (!(min < bd && max > bd)) { 47 | event.reply("有効な数値を設定して下さい。0.1~100.0").queue() 48 | return 49 | } 50 | val settings = bot.userSettingsManager.getSettings(event.user.idLong) 51 | settings.speedSetting = bd.toFloat() 52 | event.reply("速度を${bd}に設定しました。").queue() 53 | } 54 | 55 | override fun execute(event: CommandEvent) { 56 | val args = event.args 57 | 58 | if (args == null) { 59 | help(event) 60 | return 61 | } 62 | 63 | val bd = try { 64 | BigDecimal(args) 65 | } catch (e: NumberFormatException) { 66 | event.reply("数値を設定して下さい。") 67 | return 68 | } 69 | val min = BigDecimal.ZERO 70 | val max = BigDecimal("100.0") 71 | if (!(min < bd && max > bd)) { 72 | event.reply("有効な数値を設定して下さい。0.1~100.0") 73 | return 74 | } 75 | val settings = bot.userSettingsManager.getSettings(event.author.idLong) 76 | settings.speedSetting = bd.toFloat() 77 | event.reply("速度を${bd}に設定しました。") 78 | } 79 | 80 | fun help(event: CommandEvent?) { 81 | val builder = EmbedBuilder() 82 | .setTitle("setspeedコマンド") 83 | .addField("使用方法:", "$name <数値(0.0~)>", false) 84 | .addField("説明:", "読み上げの速度を設定します。読み上げ速度は、0.0以上の数値で設定して下さい。", false) 85 | event?.reply(builder.build()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.EmbedBuilder 23 | import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent 24 | import net.dv8tion.jda.api.interactions.commands.Command 25 | import net.dv8tion.jda.api.interactions.commands.OptionType 26 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 27 | import java.util.stream.Collectors 28 | import java.util.stream.Stream 29 | 30 | class SetVoiceCmd(private var bot: Bot) : SlashCommand() { 31 | private var voices: Array 32 | 33 | init { 34 | name = "setvoice" 35 | help = "声の種類を変更することができます。" 36 | guildOnly = false 37 | this.category = Category("設定") 38 | val options: MutableList = ArrayList() 39 | options.add(OptionData(OptionType.STRING, "name", "声データの名前", true, true)) 40 | this.options = options 41 | voices = bot.voiceCreation.voices.toTypedArray() 42 | } 43 | 44 | override fun execute(event: SlashCommandEvent) { 45 | if (event.getOption("name") == null) { 46 | val builder = EmbedBuilder() 47 | .setTitle("setvoiceコマンド") 48 | .addField("声データ一覧:", voices.contentToString(), false) 49 | .addField("使用方法:", "$name <声データの名前>", false) 50 | event.replyEmbeds(builder.build()).queue() 51 | return 52 | } 53 | val voiceName = event.getOption("name")!!.asString 54 | if (isValidVoice(voiceName)) { 55 | val settings = bot.userSettingsManager.getSettings(event.user.idLong) 56 | settings.voiceSetting = voiceName 57 | event.reply("声データを`$voiceName`に設定しました。").queue() 58 | } else { 59 | event.reply("有効な声データを選択して下さい。").queue() 60 | } 61 | } 62 | 63 | override fun execute(event: CommandEvent) { 64 | val voices = bot.voiceCreation.voices 65 | if (event.args.isEmpty() && event.message.attachments.isEmpty()) { 66 | val builder = EmbedBuilder() 67 | .setTitle("setvoiceコマンド") 68 | .addField("声データ一覧:", voices.toString(), false) 69 | .addField("使用方法:", "$name <声データの名前>", false) 70 | event.reply(builder.build()) 71 | return 72 | } 73 | val args = event.args 74 | if (voices.contains(args)) { 75 | val settings = bot.userSettingsManager.getSettings(event.author.idLong) 76 | settings.voiceSetting = args 77 | event.reply("声データを`$args`に設定しました。") 78 | } else { 79 | event.reply("有効な声データを選択して下さい。") 80 | } 81 | } 82 | 83 | override fun onAutoComplete(event: CommandAutoCompleteInteractionEvent) { 84 | if (event.name == "setvoice" && event.focusedOption.name == "name") { 85 | val options = Stream.of(*voices) 86 | .filter { word: String? -> word!!.startsWith(event.focusedOption.value) } // only display words that start with the user's current input 87 | .map { word: String? -> Command.Choice(word!!, word) } // map the words to choices 88 | .collect(Collectors.toList()) 89 | event.replyChoices(options).queue() 90 | } 91 | super.onAutoComplete(event) 92 | } 93 | 94 | /** 95 | * ユーザーが入力した声の名前が有効かを確認 96 | * 97 | * @param voice ユーザーが入力した声の名前 98 | * @return 名前が有効の場合は true 無効な場合は false 99 | */ 100 | private fun isValidVoice(voice: String): Boolean { 101 | for (v in voices) { 102 | if (v == voice) { 103 | return true 104 | } 105 | } 106 | return false 107 | } 108 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceQualityA.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.interactions.commands.OptionType 23 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 24 | import java.math.BigDecimal 25 | 26 | class SetVoiceQualityA(private var bot: Bot) : SlashCommand() { 27 | init { 28 | name = "setqa" 29 | help = "オールパス値の設定を変更します。" 30 | guildOnly = false 31 | this.category = Category("設定") 32 | val options: MutableList = ArrayList() 33 | options.add(OptionData(OptionType.STRING, "value", "0.1~1.0", true)) 34 | this.options = options 35 | } 36 | 37 | override fun execute(event: SlashCommandEvent) { 38 | val args = event.getOption("value")!!.asString 39 | var result: Boolean 40 | var bd: BigDecimal? = null 41 | try { 42 | //value = Float.parseFloat(args); 43 | bd = BigDecimal(args) 44 | result = true 45 | } catch (e: NumberFormatException) { 46 | result = false 47 | } 48 | if (!result) { 49 | event.reply("数値を設定して下さい。").queue() 50 | return 51 | } 52 | val min = BigDecimal("0.0") 53 | val max = BigDecimal("1.0") 54 | 55 | //if(!(0.1f <= value && value <= 1.0f)){ 56 | if (!(min < bd && max > bd)) { 57 | event.reply("有効な数値を設定して下さい。0.1~1.0").queue() 58 | return 59 | } 60 | val settings = bot.userSettingsManager.getSettings(event.user.idLong) 61 | settings.voiceQualityASetting = bd!!.toFloat() 62 | event.reply("オールパス値を${bd}に設定しました。").queue() 63 | } 64 | 65 | override fun execute(event: CommandEvent) { 66 | val args = event.args 67 | var result: Boolean 68 | var bd: BigDecimal? = null 69 | try { 70 | bd = BigDecimal(args) 71 | result = true 72 | } catch (e: NumberFormatException) { 73 | result = false 74 | } 75 | if (!result) { 76 | event.reply("数値を設定して下さい。") 77 | return 78 | } 79 | val min = BigDecimal("0.0") 80 | val max = BigDecimal("1.0") 81 | 82 | if (!(min < bd && max > bd)) { 83 | event.reply("有効な数値を設定して下さい。0.1~1.0") 84 | return 85 | } 86 | val settings = bot.userSettingsManager.getSettings(event.author.idLong) 87 | settings.voiceQualityASetting = bd!!.toFloat() 88 | event.reply("オールパス値を${bd}に設定しました。") 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/SetVoiceQualityFm.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.EmbedBuilder 23 | import net.dv8tion.jda.api.interactions.commands.OptionType 24 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 25 | import java.math.BigDecimal 26 | 27 | class SetVoiceQualityFm(private var bot: Bot) : SlashCommand() { 28 | init { 29 | name = "setqfm" 30 | help = "追加ハーフトーンの設定を変更します。" 31 | guildOnly = false 32 | this.category = Category("設定") 33 | val options: MutableList = ArrayList() 34 | options.add(OptionData(OptionType.STRING, "value", "0.1~100.0", true)) 35 | this.options = options 36 | } 37 | 38 | override fun execute(event: SlashCommandEvent) { 39 | val args = event.getOption("value")!!.asString 40 | var result: Boolean 41 | var bd: BigDecimal? = null 42 | try { 43 | //value = Float.parseFloat(args); 44 | bd = BigDecimal(args) 45 | result = true 46 | } catch (e: NumberFormatException) { 47 | result = false 48 | } 49 | if (!result) { 50 | event.reply("数値を設定して下さい。").queue() 51 | return 52 | } 53 | val min = BigDecimal("0.0") 54 | val max = BigDecimal("100.0") 55 | 56 | //if(!(0.1f <= value && value <= 100.0f)){ 57 | if (!(min < bd && max > bd)) { 58 | event.reply("有効な数値を設定して下さい。0.1~100.0").queue() 59 | return 60 | } 61 | val settings = bot.userSettingsManager.getSettings(event.user.idLong) 62 | bd?.let { settings.voiceQualityFmSetting = it.toFloat() } 63 | event.reply("追加ハーフトーンを${bd}に設定しました。").queue() 64 | } 65 | 66 | override fun execute(event: CommandEvent) { 67 | if (event.args.isEmpty() && event.message.attachments.isEmpty()) { 68 | val builder = EmbedBuilder() 69 | .setTitle("setqfmコマンド") 70 | .addField("使用方法:", "$name <数値(0.0~)>", false) 71 | .addField("説明:", "追加ハーフトーンの設定を変更します。", false) 72 | event.reply(builder.build()) 73 | return 74 | } 75 | val args = event.args 76 | var result: Boolean 77 | var bd: BigDecimal? = null 78 | try { 79 | //value = Float.parseFloat(args); 80 | bd = BigDecimal(args) 81 | result = true 82 | } catch (e: NumberFormatException) { 83 | result = false 84 | } 85 | if (!result) { 86 | event.reply("数値を設定して下さい。") 87 | return 88 | } 89 | val min = BigDecimal("0.0") 90 | val max = BigDecimal("100.0") 91 | 92 | //if(!(0.1f <= value && value <= 100.0f)){ 93 | if (!(min < bd && max > bd)) { 94 | event.reply("有効な数値を設定して下さい。0.1~100.0") 95 | return 96 | } 97 | val settings = bot.userSettingsManager.getSettings(event.author.idLong) 98 | bd?.let { settings.voiceQualityFmSetting = it.toFloat() } 99 | event.reply("追加ハーフトーンを${bd}に設定しました。") 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/SettingsCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.EmbedBuilder 23 | import java.awt.Color 24 | 25 | class SettingsCmd(private var bot: Bot) : SlashCommand() { 26 | init { 27 | guildOnly = false 28 | name = "settings" 29 | help = "現在の設定を確認します。" 30 | this.category = Category("設定") 31 | } 32 | 33 | override fun execute(event: SlashCommandEvent) { 34 | val settings = bot.userSettingsManager.getSettings(event.user.idLong) 35 | val builder = EmbedBuilder() 36 | .setColor(Color.orange) 37 | .setTitle(event.user.name + "の設定") 38 | .addField("声:", settings.voiceSetting, false) 39 | .addField("読み上げ速度:", settings.speedSetting.toString(), false) 40 | .addField("F0系列内変動の重み:", settings.intonationSetting.toString(), false) 41 | .addField("オールパス値:", settings.voiceQualityASetting.toString(), false) 42 | .addField("追加ハーフトーン:", settings.voiceQualityFmSetting.toString(), false) 43 | event.replyEmbeds(builder.build()).queue() 44 | } 45 | 46 | override fun execute(event: CommandEvent) { 47 | val settings = bot.userSettingsManager.getSettings(event.author.idLong) 48 | val builder = EmbedBuilder() 49 | .setColor(Color.orange) 50 | .setTitle(event.author.name + "の設定") 51 | .addField("声:", settings.voiceSetting, false) 52 | .addField("速度:", settings.speedSetting.toString(), false) 53 | .addField("抑揚:", settings.intonationSetting.toString(), false) 54 | .addField("声質a:", settings.voiceQualityASetting.toString(), false) 55 | .addField("声質fm:", settings.voiceQualityFmSetting.toString(), false) 56 | event.reply(builder.build()) 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/general/TranslateCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.general 17 | 18 | import com.google.gson.JsonParser 19 | import com.jagrosh.jdautilities.command.SlashCommand 20 | import com.jagrosh.jdautilities.command.SlashCommandEvent 21 | import dev.cosgy.textToSpeak.Bot 22 | import net.dv8tion.jda.api.EmbedBuilder 23 | import net.dv8tion.jda.api.interactions.commands.OptionType 24 | import net.dv8tion.jda.api.interactions.commands.build.OptionData 25 | import java.io.IOException 26 | import java.net.URI 27 | import java.net.URLEncoder 28 | import java.net.http.HttpClient 29 | import java.net.http.HttpRequest 30 | import java.net.http.HttpResponse 31 | import java.nio.charset.StandardCharsets 32 | 33 | class TranslateCmd(bot: Bot) : SlashCommand() { 34 | override fun execute(event: SlashCommandEvent) { 35 | if (deeplApiKey.isNullOrEmpty()) { 36 | event.reply( 37 | "翻訳機能が有効になっていません。\n" + 38 | "この機能を利用する場合はボット管理者が翻訳機能を有効にする必要があります。" 39 | ).setEphemeral(true) 40 | 41 | return 42 | } 43 | 44 | val text = event.getOption("text")!!.asString 45 | val sourceLang = event.getOption("source_language")!!.asString 46 | val targetLang = event.getOption("target_language")!!.asString 47 | var translation: String? = null 48 | translation = try { 49 | translateText(text, sourceLang, targetLang) 50 | } catch (e: IOException) { 51 | event.reply("翻訳に失敗しました。").setEphemeral(true).queue() 52 | return 53 | } catch (e: InterruptedException) { 54 | event.reply("翻訳に失敗しました。").setEphemeral(true).queue() 55 | return 56 | } 57 | val sourceLangName = LANGUAGES.getOrDefault(sourceLang, "Unknown") 58 | val targetLangName = LANGUAGES.getOrDefault(targetLang, "Unknown") 59 | val eBuilder = EmbedBuilder().setTitle("翻訳結果") 60 | .addField(String.format("%s (%s)", sourceLangName, sourceLang), "```$text```", false) 61 | .addField(String.format("%s (%s)", targetLangName, targetLang), "```$translation```", false) 62 | event.replyEmbeds(eBuilder.build()).setEphemeral(false).queue() 63 | } 64 | 65 | init { 66 | name = "translate" 67 | help = "入力されたテキストを任意の言語に翻訳します。" 68 | guildOnly = false 69 | deeplApiKey = bot.config.deeplApiKey 70 | this.category = Category("便利機能") 71 | val options: MutableList = ArrayList() 72 | val sourceLanguageOption = 73 | OptionData(OptionType.STRING, "source_language", "翻訳前の言語を選択してください。", true) 74 | sourceLanguageOption.addChoice("自動検出", "auto") 75 | sourceLanguageOption.addChoice("英語", "en") 76 | sourceLanguageOption.addChoice("日本語", "ja") 77 | sourceLanguageOption.addChoice("中国語", "zh") 78 | sourceLanguageOption.addChoice("ドイツ語", "de") 79 | sourceLanguageOption.addChoice("スペイン語", "es") 80 | sourceLanguageOption.addChoice("フランス語", "fr") 81 | sourceLanguageOption.addChoice("イタリア語", "it") 82 | sourceLanguageOption.addChoice("オランダ語", "nl") 83 | sourceLanguageOption.addChoice("ポーランド語", "pl") 84 | sourceLanguageOption.addChoice("ポルトガル語", "pt") 85 | sourceLanguageOption.addChoice("ロシア語", "ru") 86 | options.add(sourceLanguageOption) 87 | // 翻訳後の言語を選択するオプション 88 | val targetLanguageOption = 89 | OptionData(OptionType.STRING, "target_language", "翻訳後の言語を選択してください。", true) 90 | targetLanguageOption.addChoice("英語", "en") 91 | targetLanguageOption.addChoice("日本語", "ja") 92 | targetLanguageOption.addChoice("中国語", "zh") 93 | targetLanguageOption.addChoice("ドイツ語", "de") 94 | targetLanguageOption.addChoice("スペイン語", "es") 95 | targetLanguageOption.addChoice("フランス語", "fr") 96 | targetLanguageOption.addChoice("イタリア語", "it") 97 | targetLanguageOption.addChoice("オランダ語", "nl") 98 | targetLanguageOption.addChoice("ポーランド語", "pl") 99 | targetLanguageOption.addChoice("ポルトガル語", "pt") 100 | targetLanguageOption.addChoice("ロシア語", "ru") 101 | options.add(targetLanguageOption) 102 | val text = OptionData(OptionType.STRING, "text", "翻訳するテキストを入力してください。", true) 103 | options.add(text) 104 | this.options = options 105 | } 106 | 107 | companion object { 108 | var deeplApiKey: String? = "" 109 | 110 | @Throws(IOException::class, InterruptedException::class) 111 | private fun translateText(text: String, sourceLang: String, targetLang: String): String { 112 | val url = "https://api-free.deepl.com/v2/translate" 113 | val textParam = "text=" + URLEncoder.encode(text, StandardCharsets.UTF_8) 114 | val sourceLangParam = "source_lang=$sourceLang" 115 | val targetLangParam = "target_lang=$targetLang" 116 | var request = java.lang.String.join("&", textParam, targetLangParam) 117 | if (sourceLang != "auto") { 118 | request += "&$sourceLangParam" 119 | } 120 | val client = HttpClient.newHttpClient() 121 | val httpRequest = HttpRequest.newBuilder() 122 | .uri(URI.create(url)) 123 | .header("Content-Type", "application/x-www-form-urlencoded") 124 | .header("Authorization", "DeepL-Auth-Key $deeplApiKey") 125 | .POST(HttpRequest.BodyPublishers.ofString(request)) 126 | .build() 127 | val response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()) 128 | if (response.statusCode() != 200) { 129 | throw IOException("Translation API returned status code " + response.statusCode()) 130 | } 131 | val jsonElement = JsonParser.parseString(response.body()) 132 | val translations = jsonElement.asJsonObject.getAsJsonArray("translations") 133 | val translationObject = translations[0].asJsonObject 134 | return translationObject["text"].asString 135 | } 136 | 137 | private val LANGUAGES = mapOf( 138 | "auto" to "自動検出", 139 | "de" to "ドイツ語", 140 | "en" to "英語", 141 | "es" to "スペイン語", 142 | "fr" to "フランス語", 143 | "it" to "イタリア語", 144 | "ja" to "日本語", 145 | "nl" to "オランダ語", 146 | "pl" to "ポーランド後", 147 | "pt" to "ポルトガル語", 148 | "ru" to "ロシア語", 149 | "zh" to "中国語" 150 | ) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/commands/owner/ShutdownCmd.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.commands.owner 17 | 18 | import com.jagrosh.jdautilities.command.CommandEvent 19 | import com.jagrosh.jdautilities.command.SlashCommandEvent 20 | import dev.cosgy.textToSpeak.Bot 21 | import dev.cosgy.textToSpeak.commands.OwnerCommand 22 | 23 | class ShutdownCmd(private val bot: Bot) : OwnerCommand() { 24 | init { 25 | name = "shutdown" 26 | help = "一時ファイルを削除してボットを停止します。" 27 | guildOnly = false 28 | } 29 | 30 | override fun execute(event: SlashCommandEvent) { 31 | event.reply(event.client.warning + "シャットダウンしています...").queue() 32 | bot.shutdown() 33 | } 34 | 35 | override fun execute(event: CommandEvent) { 36 | event.reply(event.client.warning + "シャットダウンしています...") 37 | bot.shutdown() 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/entities/Prompt.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.entities 17 | 18 | import org.slf4j.LoggerFactory 19 | import java.util.* 20 | import javax.swing.JOptionPane 21 | 22 | class Prompt @JvmOverloads constructor( 23 | private val title: String, 24 | noguiMessage: String? = null, 25 | var isNoGUI: Boolean = "true".equals( 26 | System.getProperty("nogui"), 27 | ignoreCase = true 28 | ) 29 | ) { 30 | private val noguiMessage: String 31 | private var scanner: Scanner? = null 32 | 33 | init { 34 | this.noguiMessage = noguiMessage 35 | ?: "noguiモードに切り替えます。 -nogui=trueフラグを含めることで、手動でnoguiモードで起動できます。" 36 | } 37 | 38 | fun alert(level: Level?, context: String?, message: String) { 39 | if (isNoGUI) { 40 | val log = LoggerFactory.getLogger(context) 41 | when (level) { 42 | Level.WARNING -> log.warn(message) 43 | Level.ERROR -> log.error(message) 44 | Level.INFO -> log.info(message) 45 | else -> log.info(message) 46 | } 47 | } else { 48 | try { 49 | var option = 0 50 | when (level) { 51 | Level.INFO -> option = JOptionPane.INFORMATION_MESSAGE 52 | Level.WARNING -> option = JOptionPane.WARNING_MESSAGE 53 | Level.ERROR -> {} 54 | else -> option = JOptionPane.PLAIN_MESSAGE 55 | } 56 | JOptionPane.showMessageDialog(null, "

$message", title, option) 57 | } catch (e: Exception) { 58 | isNoGUI = true 59 | alert(Level.WARNING, context, noguiMessage) 60 | alert(level, context, message) 61 | } 62 | } 63 | } 64 | 65 | fun prompt(content: String?): String? { 66 | return if (isNoGUI) { 67 | if (scanner == null) scanner = Scanner(System.`in`) 68 | try { 69 | println(content) 70 | if (scanner!!.hasNextLine()) scanner!!.nextLine() else null 71 | } catch (e: Exception) { 72 | alert(Level.ERROR, title, "コマンドラインから入力を読み込めません。") 73 | e.printStackTrace() 74 | null 75 | } 76 | } else { 77 | try { 78 | JOptionPane.showInputDialog(null, content, title, JOptionPane.QUESTION_MESSAGE) 79 | } catch (e: Exception) { 80 | isNoGUI = true 81 | alert(Level.WARNING, title, noguiMessage) 82 | prompt(content) 83 | } 84 | } 85 | } 86 | 87 | enum class Level { 88 | INFO, WARNING, ERROR 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/gui/ConsolePanel.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.gui 17 | 18 | import java.awt.Dimension 19 | import java.awt.GridLayout 20 | import java.io.PrintStream 21 | import javax.swing.JPanel 22 | import javax.swing.JScrollPane 23 | import javax.swing.JTextArea 24 | 25 | class ConsolePanel : JPanel() { 26 | init { 27 | val text = JTextArea() 28 | text.lineWrap = true 29 | text.wrapStyleWord = true 30 | text.isEditable = false 31 | val con = PrintStream(TextAreaOutputStream(text)) 32 | System.setOut(con) 33 | System.setErr(con) 34 | val pane = JScrollPane() 35 | pane.setViewportView(text) 36 | super.setLayout(GridLayout(1, 1)) 37 | super.add(pane) 38 | super.setPreferredSize(Dimension(400, 300)) 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/gui/TextAreaOutputStream.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.gui 17 | 18 | import java.awt.EventQueue 19 | import java.io.OutputStream 20 | import java.io.UnsupportedEncodingException 21 | import java.nio.charset.Charset 22 | import java.util.* 23 | import javax.swing.JTextArea 24 | 25 | /** 26 | * @author Kosugi_kun 27 | */ 28 | class TextAreaOutputStream @JvmOverloads constructor(txtara: JTextArea, maxlin: Int = 1000) : OutputStream() { 29 | private val oneByte: ByteArray 30 | private var appender: Appender? 31 | 32 | init { 33 | require(maxlin >= 1) { "TextAreaOutputStreamの最大行数は正数でなければなりません(value=$maxlin)" } 34 | oneByte = ByteArray(1) 35 | appender = Appender(txtara, maxlin) 36 | } 37 | 38 | @Synchronized 39 | fun clear() { 40 | if (appender != null) { 41 | appender!!.clear() 42 | } 43 | } 44 | 45 | @Synchronized 46 | override fun close() { 47 | appender = null 48 | } 49 | 50 | @Synchronized 51 | override fun flush() { 52 | /* empty */ 53 | } 54 | 55 | @Synchronized 56 | override fun write(`val`: Int) { 57 | oneByte[0] = `val`.toByte() 58 | write(oneByte, 0, 1) 59 | } 60 | 61 | @Synchronized 62 | override fun write(ba: ByteArray) { 63 | write(ba, 0, ba.size) 64 | } 65 | 66 | @Synchronized 67 | override fun write(ba: ByteArray, str: Int, len: Int) { 68 | if (appender != null) { 69 | appender!!.append(bytesToString(ba, str, len)) 70 | } 71 | } 72 | 73 | internal class Appender(private val textArea: JTextArea, private val maxLines: Int) : Runnable { 74 | private val lengths: LinkedList = LinkedList() 75 | private val values: MutableList 76 | private var curLength = 0 77 | private var clear = false 78 | private var queue = true 79 | 80 | init { 81 | values = ArrayList() 82 | } 83 | 84 | @Synchronized 85 | fun append(`val`: String) { 86 | values.add(`val`) 87 | if (queue) { 88 | queue = false 89 | EventQueue.invokeLater(this) 90 | } 91 | } 92 | 93 | @Synchronized 94 | fun clear() { 95 | clear = true 96 | curLength = 0 97 | lengths.clear() 98 | values.clear() 99 | if (queue) { 100 | queue = false 101 | EventQueue.invokeLater(this) 102 | } 103 | } 104 | 105 | @Synchronized 106 | override fun run() { 107 | if (clear) { 108 | textArea.text = "" 109 | } 110 | values.stream() 111 | .peek { `val`: String -> curLength += `val`.length } 112 | .peek { `val`: String -> 113 | if (`val`.endsWith(EOL1) || `val`.endsWith(EOL2)) { 114 | if (lengths.size >= maxLines) { 115 | textArea.replaceRange("", 0, lengths.removeFirst()) 116 | } 117 | lengths.addLast(curLength) 118 | curLength = 0 119 | } 120 | }.forEach { str: String? -> textArea.append(str) } 121 | values.clear() 122 | clear = false 123 | queue = true 124 | } 125 | 126 | companion object { 127 | private const val EOL1 = "\n" 128 | private val EOL2 = System.getProperty("line.separator", EOL1) 129 | } 130 | } 131 | 132 | companion object { 133 | private fun bytesToString(ba: ByteArray, str: Int, len: Int): String { 134 | return try { 135 | String(ba, str, len, Charset.defaultCharset()) 136 | } catch (thr: UnsupportedEncodingException) { 137 | String(ba, str, len) 138 | } 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/listeners/CommandAudit.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.listeners 17 | 18 | import com.jagrosh.jdautilities.command.Command 19 | import com.jagrosh.jdautilities.command.CommandEvent 20 | import com.jagrosh.jdautilities.command.CommandListener 21 | import dev.cosgy.textToSpeak.TextToSpeak 22 | import net.dv8tion.jda.api.entities.channel.ChannelType 23 | import org.slf4j.LoggerFactory 24 | 25 | class CommandAudit : CommandListener { 26 | override fun onCommand(event: CommandEvent, command: Command) { 27 | if (TextToSpeak.COMMAND_AUDIT_ENABLED) { 28 | val logger = LoggerFactory.getLogger("CommandAudit") 29 | val textFormat = 30 | if (event.isFromType(ChannelType.PRIVATE)) "%s%s で %s (%s) がコマンド %s を実行しました" else "%s の #%s で %s (%s) がコマンド %s を実行しました" 31 | logger.info( 32 | String.format( 33 | textFormat, 34 | if (event.isFromType(ChannelType.PRIVATE)) "DM" else event.guild.name, 35 | if (event.isFromType(ChannelType.PRIVATE)) "" else event.textChannel.name, 36 | event.author.name, event.author.id, 37 | event.message.contentDisplay 38 | ) 39 | ) 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/listeners/MessageListener.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.listeners 17 | 18 | import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler 19 | import com.sedmelluq.discord.lavaplayer.tools.FriendlyException 20 | import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist 21 | import com.sedmelluq.discord.lavaplayer.track.AudioTrack 22 | import dev.cosgy.textToSpeak.Bot 23 | import dev.cosgy.textToSpeak.audio.AudioHandler 24 | import dev.cosgy.textToSpeak.audio.QueuedTrack 25 | import dev.cosgy.textToSpeak.utils.ReadChannel 26 | import net.dv8tion.jda.api.entities.channel.ChannelType 27 | import net.dv8tion.jda.api.events.message.MessageReceivedEvent 28 | import net.dv8tion.jda.api.events.session.ReadyEvent 29 | import net.dv8tion.jda.api.hooks.ListenerAdapter 30 | import java.io.IOException 31 | 32 | class MessageListener(private val bot: Bot) : ListenerAdapter() { 33 | override fun onMessageReceived(event: MessageReceivedEvent) { 34 | val startTime = System.currentTimeMillis() 35 | event.jda 36 | event.responseNumber 37 | 38 | //イベント固有の情報 39 | val author = event.author //メッセージを送信したユーザー 40 | val message = event.message //受信したメッセージ。 41 | event.channel //メッセージが送信されたMessageChannel 42 | var msg = message.contentDisplay 43 | //人間が読める形式のメッセージが返されます。 クライアントに表示されるものと同様。 44 | val isBot = author.isBot 45 | //if(Arrays.asList(mentionedUsers).contains()) 46 | //メッセージを送信したユーザーがBOTであるかどうかを判断。 47 | if (event.isFromType(ChannelType.TEXT)) { 48 | if (isBot) return 49 | val guild = event.guild 50 | val textChannel = event.guildChannel.asTextChannel() 51 | var settingText = bot.settingsManager.getSettings(event.guild).getTextChannel(event.guild) 52 | if (!guild.audioManager.isConnected) { 53 | return 54 | } 55 | val prefix = if (bot.config.prefix == "@mention") "@" + event.jda.selfUser.name + " " else bot.config.prefix 56 | if (prefix?.let { msg.startsWith(it) } == true) { 57 | return 58 | } 59 | if (textChannel !== settingText) { 60 | if (settingText == null) { 61 | settingText = event.guild.getTextChannelById(ReadChannel.getChannel(event.guild.idLong)!!) 62 | } 63 | } 64 | 65 | // URLを置き換え 66 | msg = msg.replace("(http://|https://)[\\w.\\-/:#?=&;%~+]+".toRegex(), "ゆーあーるえる") 67 | message.getStickers().forEach { sticker -> msg += " " + sticker.getName() } 68 | if (textChannel === settingText) { 69 | val settings = bot.settingsManager.getSettings(guild) 70 | if (settings.isReadName()) { 71 | 72 | var nic = event.member?.nickname 73 | 74 | nic = nic ?: author.effectiveName 75 | 76 | msg = "${if (settings.isReadNic()) nic else author.effectiveName} " + msg 77 | } 78 | val vc = bot.voiceCreation 79 | val file: String? = try { 80 | vc.createVoice(guild, author, msg) 81 | } catch (e: IOException) { 82 | throw RuntimeException(e) 83 | } catch (e: InterruptedException) { 84 | throw RuntimeException(e) 85 | } 86 | bot.playerManager.loadItemOrdered(event.guild, file, ResultHandler(event)) 87 | 88 | //textChannel.sendMessage(author.getName() + "が、「"+ msg +"」と送信しました。").queue(); 89 | } 90 | } 91 | 92 | // 終了時刻を記録 93 | val endTime = System.currentTimeMillis() 94 | 95 | // 実行時間を計算 96 | val executionTime = endTime - startTime 97 | } 98 | 99 | override fun onReady(e: ReadyEvent) { 100 | bot.readyJDA() 101 | } 102 | 103 | private class ResultHandler(private val event: MessageReceivedEvent) : AudioLoadResultHandler { 104 | private fun loadSingle(track: AudioTrack) { 105 | val handler = event.guild.audioManager.sendingHandler as AudioHandler? 106 | handler!!.addTrack(QueuedTrack(track, event.author)) 107 | } 108 | 109 | override fun trackLoaded(track: AudioTrack) { 110 | loadSingle(track) 111 | } 112 | 113 | override fun playlistLoaded(playlist: AudioPlaylist) {} 114 | override fun noMatches() {} 115 | override fun loadFailed(throwable: FriendlyException) {} 116 | } 117 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/queue/FairQueue.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.queue 17 | 18 | class FairQueue { 19 | private val list: MutableList = ArrayList() 20 | private val set: MutableSet = HashSet() 21 | fun add(item: T): Int { 22 | var lastIndex: Int = list.size - 1 23 | while (lastIndex > -1) { 24 | if (list[lastIndex]!!.identifier == item!!.identifier) break 25 | lastIndex-- 26 | } 27 | lastIndex++ 28 | set.clear() 29 | while (lastIndex < list.size) { 30 | if (set.contains(list[lastIndex]!!.identifier)) break 31 | set.add(list[lastIndex]!!.identifier) 32 | lastIndex++ 33 | } 34 | list.add(lastIndex, item) 35 | return lastIndex 36 | } 37 | 38 | fun addAt(index: Int, item: T) { 39 | if (index >= list.size) list.add(item) else list.add(index, item) 40 | } 41 | 42 | fun size(): Int { 43 | return list.size 44 | } 45 | 46 | fun pull(): T { 47 | return list.removeAt(0) 48 | } 49 | 50 | val isEmpty: Boolean 51 | get() = list.isEmpty() 52 | 53 | fun getList(): List { 54 | return list 55 | } 56 | 57 | operator fun get(index: Int): T { 58 | return list[index] 59 | } 60 | 61 | fun remove(index: Int): T { 62 | return list.removeAt(index) 63 | } 64 | 65 | fun removeAll(identifier: Long): Int { 66 | var count = 0 67 | for (i in list.indices.reversed()) { 68 | if (list[i]!!.identifier == identifier) { 69 | list.removeAt(i) 70 | count++ 71 | } 72 | } 73 | return count 74 | } 75 | 76 | fun clear() { 77 | list.clear() 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/queue/Queueable.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.queue 17 | 18 | interface Queueable { 19 | val identifier: Long 20 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/settings/Settings.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.settings 17 | 18 | import com.jagrosh.jdautilities.command.GuildSettingsProvider 19 | import net.dv8tion.jda.api.entities.Guild 20 | import net.dv8tion.jda.api.entities.channel.concrete.TextChannel 21 | 22 | class Settings : GuildSettingsProvider { 23 | private val manager: SettingsManager 24 | var textId: Long = 0 25 | var prefix: String? 26 | var volume: Int 27 | private var readName: Boolean 28 | private var joinAndLeaveRead: Boolean 29 | private var readNic: Boolean 30 | 31 | constructor( 32 | manager: SettingsManager, 33 | textId: String?, 34 | prefix: String?, 35 | volume: Int, 36 | readName: Boolean, 37 | joinAndLeaveRead: Boolean, 38 | readNic: Boolean, 39 | ) { 40 | this.manager = manager 41 | try { 42 | this.textId = textId?.toLong() ?: 0 43 | } catch (e: NumberFormatException) { 44 | this.textId = 0 45 | } 46 | this.prefix = prefix 47 | this.volume = volume 48 | this.readName = readName 49 | this.joinAndLeaveRead = joinAndLeaveRead 50 | this.readNic = readNic 51 | } 52 | 53 | constructor( 54 | manager: SettingsManager, 55 | textId: Long, 56 | prefix: String?, 57 | volume: Int, 58 | readName: Boolean, 59 | joinAndLeaveRead: Boolean, 60 | readNic: Boolean 61 | ) { 62 | this.manager = manager 63 | this.textId = textId 64 | this.prefix = prefix 65 | this.volume = volume 66 | this.readName = readName 67 | this.joinAndLeaveRead = joinAndLeaveRead 68 | this.readNic = readNic 69 | } 70 | 71 | fun getTextChannel(guild: Guild?): TextChannel? { 72 | return guild?.getTextChannelById(textId) 73 | } 74 | 75 | fun setTextChannel(tc: TextChannel?) { 76 | textId = tc?.idLong ?: 0 77 | manager.writeSettings() 78 | } 79 | 80 | override fun getPrefixes(): Collection { 81 | return if (prefix == null) emptySet() else setOf(prefix) 82 | } 83 | 84 | fun isReadName(): Boolean { 85 | return readName 86 | } 87 | 88 | fun setReadName(readName: Boolean) { 89 | this.readName = readName 90 | manager.writeSettings() 91 | } 92 | 93 | fun isJoinAndLeaveRead(): Boolean { 94 | return joinAndLeaveRead 95 | } 96 | 97 | fun setJoinAndLeaveRead(joinAndLeaveRead: Boolean) { 98 | this.joinAndLeaveRead = joinAndLeaveRead 99 | manager.writeSettings() 100 | } 101 | 102 | fun isReadNic(): Boolean { 103 | return readNic 104 | } 105 | 106 | fun setReadNic(readNic: Boolean) { 107 | this.readNic = readNic 108 | manager.writeSettings() 109 | } 110 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/settings/SettingsManager.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.settings 17 | 18 | import com.jagrosh.jdautilities.command.GuildSettingsManager 19 | import dev.cosgy.textToSpeak.utils.OtherUtil 20 | import net.dv8tion.jda.api.entities.Guild 21 | import org.json.JSONException 22 | import org.json.JSONObject 23 | import org.slf4j.LoggerFactory 24 | import java.io.IOException 25 | import java.nio.file.Files 26 | import java.util.function.Consumer 27 | 28 | class SettingsManager : GuildSettingsManager { 29 | private val settings: HashMap = HashMap() 30 | 31 | init { 32 | try { 33 | val loadedSettings = JSONObject(String(Files.readAllBytes(OtherUtil.getPath("serversettings.json")))) 34 | loadedSettings.keySet().forEach { id -> 35 | val value = loadedSettings.getJSONObject(id) 36 | settings[id.toLong()] = Settings( 37 | this, 38 | if (value.has("text_channel_id")) value.getString("text_channel_id") else null, 39 | if (value.has("prefix")) value.getString("prefix") else null, 40 | if (value.has("volume")) value.getInt("volume") else 50, 41 | value.has("read_name") && value.getBoolean("read_name"), 42 | value.has("join_and_leave_read") && value.getBoolean("join_and_leave_read"), 43 | value.has("read_nic") && value.getBoolean("read_nic") 44 | ) 45 | } 46 | } catch (e: IOException) { 47 | LoggerFactory.getLogger("Settings") 48 | .warn("サーバー設定を読み込めませんでした(まだ設定がない場合は正常です): $e") 49 | } catch (e: JSONException) { 50 | LoggerFactory.getLogger("Settings") 51 | .warn("サーバー設定を読み込めませんでした(まだ設定がない場合は正常です): $e") 52 | } 53 | } 54 | 55 | override fun getSettings(guild: Guild): Settings { 56 | return getSettings(guild.idLong) 57 | } 58 | 59 | fun getSettings(guildId: Long): Settings { 60 | return settings.computeIfAbsent(guildId) { _: Long? -> createDefaultSettings() } 61 | } 62 | 63 | /** 64 | * デフォルト設定のデータを作って返す。 65 | * 66 | * @return 作成されたデフォルト設定 67 | */ 68 | private fun createDefaultSettings(): Settings { 69 | return Settings(this, 0, null, 50, readName = false, joinAndLeaveRead = false, readNic = false) 70 | } 71 | 72 | /** 73 | * 設定をファイルに書き込む 74 | */ 75 | fun writeSettings() { 76 | val obj = JSONObject() 77 | settings.keys.forEach(Consumer { key: Long -> 78 | val o = JSONObject() 79 | val s = settings[key] 80 | if (s!!.textId != 0L) o.put("text_channel_id", s.textId.toString()) 81 | if (s.prefix != null) o.put("prefix", s.prefix) 82 | if (s.volume != 50) o.put("volume", s.volume) 83 | if (s.isReadName()) o.put("read_name", s.isReadName()) 84 | if (s.isJoinAndLeaveRead()) o.put("join_and_leave_read", s.isJoinAndLeaveRead()) 85 | if (s.isReadNic()) o.put("read_nic", s.isReadNic()) 86 | obj.put(key.toString(), o) 87 | }) 88 | try { 89 | Files.write(OtherUtil.getPath("serversettings.json"), obj.toString(4).toByteArray()) 90 | } catch (ex: IOException) { 91 | LoggerFactory.getLogger("Settings").warn("ファイルへの書き込みに失敗しました: $ex") 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/settings/UserSettings.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.settings 17 | 18 | class UserSettings( 19 | private val manager: UserSettingsManager, 20 | private val userId: Long, 21 | private var voice: String, 22 | private var speed: Float, 23 | private var intonation: Float, 24 | private var voiceQualityA: Float, 25 | private var voiceQualityFm: Float 26 | ) { 27 | var voiceSetting: String 28 | get() = voice 29 | set(value) { 30 | voice = value 31 | manager.saveSetting(userId) 32 | } 33 | 34 | var speedSetting: Float 35 | get() = speed 36 | set(value) { 37 | speed = value 38 | manager.saveSetting(userId) 39 | } 40 | 41 | var intonationSetting: Float 42 | get() = intonation 43 | set(value) { 44 | intonation = value 45 | manager.saveSetting(userId) 46 | } 47 | 48 | var voiceQualityASetting: Float 49 | get() = voiceQualityA 50 | set(value) { 51 | voiceQualityA = value 52 | manager.saveSetting(userId) 53 | } 54 | 55 | var voiceQualityFmSetting: Float 56 | get() = voiceQualityFm 57 | set(value) { 58 | voiceQualityFm = value 59 | manager.saveSetting(userId) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/settings/UserSettingsManager.kt: -------------------------------------------------------------------------------- 1 | package dev.cosgy.textToSpeak.settings 2 | 3 | import dev.cosgy.textToSpeak.utils.OtherUtil 4 | import org.apache.commons.io.FileUtils 5 | import org.slf4j.LoggerFactory 6 | import java.io.IOException 7 | import java.nio.charset.StandardCharsets 8 | import java.sql.Connection 9 | import java.sql.DriverManager 10 | import java.sql.SQLException 11 | 12 | ////////////////////////////////////////////////////////////////////////////////////////// 13 | // Copyright 2023 Cosgy Dev / 14 | // / 15 | // Licensed under the Apache License, Version 2.0 (the "License"); / 16 | // you may not use this file except in compliance with the License. / 17 | // You may obtain a copy of the License at / 18 | // / 19 | // http://www.apache.org/licenses/LICENSE-2.0 / 20 | // / 21 | // Unless required by applicable law or agreed to in writing, software / 22 | // distributed under the License is distributed on an "AS IS" BASIS, / 23 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 24 | // See the License for the specific language governing permissions and / 25 | // limitations under the License. / 26 | ////////////////////////////////////////////////////////////////////////////////////////// 27 | class UserSettingsManager { 28 | private val settings: HashMap = HashMap() 29 | private val logger = LoggerFactory.getLogger(this.javaClass) 30 | private var connection: Connection? = null 31 | 32 | init { 33 | val path = OtherUtil.getPath("UserData.sqlite") 34 | if (!path.toFile().exists()) { 35 | val original = OtherUtil.loadResource(this, "UserData.sqlite") 36 | try { 37 | FileUtils.writeStringToFile(path.toFile(), original, StandardCharsets.UTF_8) 38 | logger.info("データベースファイルが存在しなかったためファイルを作成しました。") 39 | } catch (e: IOException) { 40 | logger.error("データベースファイルを作成できませんでした。", e) 41 | } 42 | } 43 | try { 44 | connection = DriverManager.getConnection("jdbc:sqlite:UserData.sqlite") 45 | val statement = connection!!.createStatement() 46 | val sql = 47 | "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)" 48 | statement.execute(sql) 49 | val rs = statement.executeQuery("select * from settings") 50 | while (rs.next()) { 51 | settings[rs.getLong(1)] = UserSettings( 52 | this, 53 | rs.getLong(1), 54 | rs.getString(2), 55 | rs.getFloat(3), 56 | rs.getFloat(4), 57 | rs.getFloat(5), 58 | rs.getFloat(6) 59 | ) 60 | } 61 | } catch (throwables: SQLException) { 62 | logger.error("データベースに接続できませんでした。", throwables) 63 | } 64 | } 65 | 66 | fun getSettings(userId: Long): UserSettings { 67 | return settings.computeIfAbsent(userId) { _: Long -> createDefaultSettings(userId) } 68 | } 69 | 70 | private fun createDefaultSettings(userId: Long): UserSettings { 71 | return UserSettings(this, userId, "mei_normal", 1.0f, 1.0f, 0.5f, 2.0f) 72 | } 73 | 74 | fun saveSetting(userId: Long) { 75 | val sql = 76 | "REPLACE INTO settings (id, voice, speed, intonation, voiceQualityA, voiceQualityFm) VALUES (?,?,?,?,?,?)" 77 | val settings = settings[userId] 78 | try { 79 | connection!!.prepareStatement(sql).use { ps -> 80 | ps.setLong(1, userId) 81 | ps.setString(2, settings!!.voiceSetting) 82 | ps.setFloat(3, settings.speedSetting) 83 | ps.setFloat(4, settings.intonationSetting) 84 | ps.setFloat(5, settings.voiceQualityASetting) 85 | ps.setFloat(6, settings.voiceQualityFmSetting) 86 | logger.debug(ps.toString()) 87 | ps.executeUpdate() 88 | } 89 | } catch (throwables: SQLException) { 90 | logger.error("設定を保存できませんでした。", throwables) 91 | } 92 | } 93 | 94 | fun closeConnection() { 95 | try { 96 | connection!!.close() 97 | logger.info("データベース接続を終了しました。") 98 | } catch (e: SQLException) { 99 | logger.error("データベース接続を終了できませんでした。", e) 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/utils/FormatUtil.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.utils 17 | 18 | import net.dv8tion.jda.api.entities.channel.concrete.TextChannel 19 | 20 | object FormatUtil { 21 | fun listOfTChannels(list: List, query: String): String { 22 | val out = StringBuilder(" 複数のテキストチャンネルで${query}が一致しました。:") 23 | var i = 0 24 | while (i < 6 && i < list.size) { 25 | out.append("\n - ").append(list[i].name).append(" (<#").append(list[i].id).append(">)") 26 | i++ 27 | } 28 | if (list.size > 6) out.append("\n**と ").append(list.size - 6).append(" など...**") 29 | return out.toString() 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/dev/cosgy/textToSpeak/utils/ReadChannel.kt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////// 2 | // Copyright 2023 Cosgy Dev / 3 | // / 4 | // Licensed under the Apache License, Version 2.0 (the "License"); / 5 | // you may not use this file except in compliance with the License. / 6 | // You may obtain a copy of the License at / 7 | // / 8 | // http://www.apache.org/licenses/LICENSE-2.0 / 9 | // / 10 | // Unless required by applicable law or agreed to in writing, software / 11 | // distributed under the License is distributed on an "AS IS" BASIS, / 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. / 13 | // See the License for the specific language governing permissions and / 14 | // limitations under the License. / 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | package dev.cosgy.textToSpeak.utils 17 | 18 | object ReadChannel { 19 | private val chat = HashMap() 20 | fun setChannel(guild: Long, textChannel: Long) { 21 | chat[guild] = textChannel 22 | } 23 | 24 | fun getChannel(guild: Long): Long? { 25 | return chat[guild] 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/resources/UserData.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cosgy-Dev/TextToSpeakBot/1b066a3109e47c04ae1837506c79ea8cca69eaa6/src/main/resources/UserData.sqlite -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=DEBUG, console 2 | log4j.logger.xxx=DEBUG, console 3 | 4 | log4j.appender.console=org.apache.log4j.ConsoleAppender 5 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.console.layout.ConversionPattern=%d [%-5p-%c] %m%n -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | %nopex[%d{HH:mm:ss}] [%level] [%logger{0}]: %msg%n%ex 26 | 27 | 28 | 29 | 30 | ${logDir}${fileName}.log 31 | 32 | ${logDir}${fileName}.%d{yyyy-MM-dd}.log 33 | 10 34 | 35 | 36 | 37 | 38 | %nopex[%d{HH:mm:ss}] [%level] [%logger{0}]: %msg%n%ex 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | /// START OF TEXT TO SPEAK BOT CONFIG /// 2 | //______________________________________________________________ 3 | //=================== 4 | // TextToSpeak Bot\u00E3\u0081\u00AE\u00E8\u00A8\u00AD\u00E5\u00AE\u009A 5 | //=================== 6 | // 7 | // //で始まる行は無視されます。 8 | // トークンと所有者を設定しなければなりません。 9 | // 設定しない場合、他のすべての項目にはデフォルトがあります。 10 | // Notepad++などのエディタで編集する事を推奨します。 11 | // ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 12 | 13 | // この項目は、Botがログインするためのトークンを設定します 14 | // 入力するトークンはBotトークンでなければなりません(ユーザートークンは機能しません) 15 | // Botトークンを取得する方法がわからない場合は、こちらのガイドを参照してください。 16 | token = ボットトークンをここに貼り付け 17 | 18 | 19 | // この項目は、Botの所有者を設定します 20 | // これは、所有者のID(17〜18桁の数字)である必要があります。 21 | owner = 所有者IDをここに貼り付け 22 | 23 | 24 | // この項目は、Botの接頭辞を設定します 25 | // 接頭辞はコマンドを制御するために使用されます 26 | // !!を使用すると、joinコマンドは !!join になります。 27 | // デフォルトの接頭辞はBotの@mentionになります (例: @Botname join) 28 | prefix = "@mention" 29 | 30 | // この項目は、Botの接頭辞(副)を設定します 31 | // この項目を設定すると、上で設定した接頭辞の他に、ここで設定した接頭辞も使用できるようになります。 32 | // NONE または なし にすると接頭辞(副)を無効にします。 33 | // デフォルトは なし です。 34 | altprefix = "なし" 35 | 36 | // ヘルプメッセージをDMに送信するか設定できます。trueに設定するとDMに送信されるようになります。 37 | // デフォルトは true です。 38 | helptodm = true 39 | 40 | // この項目を設定すると、Botのゲーム欄を変更できます。 41 | // ゲーム欄は、「Playing ~」、「Listening to ~」、または「Watching ~」にすることができます。 42 | // ※ ~にはゲーム名を入力してください。 43 | // デフォルトの設定を使用するには DEFAULT と入力してください。 44 | // デフォルトは Playing です。ゲーム欄に何も表示しない場合は、NONE または なし と入力してください。 45 | game = "DEFAULT" 46 | 47 | 48 | // この項目を設定すると、Botのオンライン状態を設定できます。 49 | // 使用できる設定: ONLINE(オンライン) IDLE(退席中) DND(取り込み中) INVISIBLE(オンライン状態を隠す) 50 | // デフォルトは ONLINE です。 51 | status = ONLINE 52 | 53 | // この項目は、Botが実行中に新しいバージョンが利用可能になったとき、Botが所有者にDMを送信するかどうかを設定します。 54 | // 新しいバージョンが利用可能になったとき通知する場合は true を、通知しない場合は false を設定してください。 55 | // デフォルトは true です。 56 | updatealerts = true 57 | 58 | // Open JTalkの辞書データが保存されているディレクトリのパスを設定して下さい。 59 | dictionary = "/var/lib/mecab/dic/open-jtalk/naist-jdic" 60 | 61 | // 拡張子が htsvoice のファイルが保存されているディレクトリのパスを設定して下さい。 62 | // フォルダの中は階層分けせずにhtsvoiceを保存して下さい。 63 | voiceDirectory ="/usr/share/hts-voice" 64 | 65 | // ボイスチャンネルに参加しているユーザーがボットのみになった場合設定した秒数後に自動で退出します。 66 | // この設定は、0の場合無効になります。 67 | alonetimeuntilstop = 0 68 | 69 | // 読み上げるメッセージの最大文字数を設定します。読み上げる文字数が最大数に到達した場合、それ以降は「以下略」と読み上げます。 70 | // 0の場合、設定は無効になります。 71 | maxmessagecount = 0 72 | 73 | // Windows環境で、Open JTalkがインストールされているディレクトリを入力して下さい。 74 | // 注意: 75 | // Open JTalkの実行ファイル名は、必ず`open_jtalk.exe`に設定してください。 76 | // Windows環境でのホストは、推奨していません。 77 | winjtalkdir = "" 78 | 79 | // ファイルを保存する際に文字コードを強制的にUTF-8にします。 80 | // この設定はWindowsのみで有効です。 81 | forceutf-8 = false 82 | 83 | // 翻訳機能を利用する際にはここにDeepLのAPIKeyを設定ください。 84 | deeplapikey = "" 85 | 86 | /// END OF TEXT TO SPEAK BOT CONFIG /// --------------------------------------------------------------------------------