├── .dockerignore ├── .github ├── renovate.json5 └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── crates ├── koe-audio │ ├── Cargo.toml │ └── src │ │ ├── audio.rs │ │ ├── ffmpeg.rs │ │ └── lib.rs ├── koe-call │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── koe-config │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── koe-db │ ├── Cargo.toml │ └── src │ │ ├── dict.rs │ │ ├── lib.rs │ │ └── voice.rs ├── koe-speech │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── speech.rs │ │ └── voicevox.rs └── koe │ ├── Cargo.toml │ └── src │ ├── app_state.rs │ ├── command │ ├── handler.rs │ ├── mod.rs │ ├── model.rs │ ├── parser.rs │ └── setup.rs │ ├── component_interaction │ ├── custom_id.rs │ ├── handler.rs │ └── mod.rs │ ├── error.rs │ ├── event_handler.rs │ ├── main.rs │ ├── message │ ├── handler.rs │ ├── mod.rs │ └── read.rs │ ├── regex.rs │ └── voice_state │ ├── handler.rs │ └── mod.rs ├── deployment ├── config │ ├── koe.yaml │ ├── redis.conf │ └── voicevox_presets.yaml └── docker-compose.yml ├── devtools ├── .gitattributes ├── .gitignore ├── .husky │ └── commit-msg ├── .prettierignore ├── .prettierrc.js ├── .release-it.js ├── .yarn │ └── releases │ │ └── yarn-4.2.2.cjs ├── .yarnrc.yml ├── commitlint.config.js ├── package.json ├── src │ └── generateDockerTags.js ├── template │ └── changelog │ │ └── header.hbs └── yarn.lock └── docs ├── logo ├── icon.png ├── icon.svg ├── logo.png └── logo.svg ├── release_procedure.md ├── setup_guide.md └── user_guide.md /.dockerignore: -------------------------------------------------------------------------------- 1 | # Rust build output 2 | target 3 | 4 | # CLion 5 | .idea 6 | 7 | # Files not required for Docker build 8 | .git 9 | .github 10 | deployment 11 | deployment_dev 12 | devtools 13 | docs 14 | .gitignore 15 | docker-compose.yml 16 | LICENSE 17 | README.md 18 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: 'https://docs.renovatebot.com/renovate-schema.json', 3 | extends: [ 4 | 'config:base', 5 | ':semanticCommits', 6 | ':semanticCommitTypeAll(chore)', 7 | ':prHourlyLimitNone', 8 | ], 9 | timezone: 'Asia/Tokyo', 10 | schedule: ['before 9am on Saturday'], 11 | packageRules: [ 12 | { 13 | matchUpdateTypes: ['minor', 'patch'], 14 | matchCurrentVersion: '!/^0/', 15 | automerge: true, 16 | }, 17 | { 18 | matchManagers: ['npm'], 19 | rangeStrategy: 'bump', 20 | }, 21 | { 22 | matchManagers: ['dockerfile'], 23 | matchPackageNames: ['rust'], 24 | enabled: false, 25 | }, 26 | ], 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUST_VERSION: 1.73.0 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | format: 18 | runs-on: ubuntu-22.04 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Setup Rust 23 | run: | 24 | rustup set profile minimal 25 | rustup toolchain install "$RUST_VERSION" --component rustfmt 26 | rustup override set "$RUST_VERSION" 27 | 28 | - name: Check code format 29 | run: cargo fmt --all -- --check 30 | 31 | lint: 32 | runs-on: ubuntu-22.04 33 | steps: 34 | - uses: actions/checkout@v4 35 | 36 | - name: Setup Rust 37 | run: | 38 | rustup set profile minimal 39 | rustup toolchain install "$RUST_VERSION" --component clippy 40 | rustup override set "$RUST_VERSION" 41 | 42 | - uses: Swatinem/rust-cache@v2 43 | 44 | - name: Run lint 45 | run: cargo clippy --all-targets --all-features -- -D warnings 46 | 47 | check-next-version: 48 | if: github.ref == 'refs/heads/main' 49 | runs-on: ubuntu-22.04 50 | steps: 51 | - uses: actions/checkout@v4 52 | with: 53 | fetch-depth: 0 54 | 55 | - uses: actions/setup-node@v4 56 | with: 57 | node-version: 18 58 | 59 | - name: Setup devtools 60 | run: yarn install --immutable 61 | working-directory: ./devtools 62 | 63 | - name: Check next version 64 | run: yarn run print-next-version 65 | working-directory: ./devtools 66 | 67 | docker-build-push: 68 | needs: [format, lint] 69 | runs-on: ubuntu-22.04 70 | permissions: 71 | contents: read 72 | packages: write 73 | steps: 74 | - uses: actions/checkout@v4 75 | 76 | - name: Set up Docker Buildx 77 | uses: docker/setup-buildx-action@v3 78 | 79 | - name: Login to GitHub Container Registry 80 | if: github.ref == 'refs/heads/main' 81 | uses: docker/login-action@v3 82 | with: 83 | registry: ghcr.io 84 | username: ${{ github.repository_owner }} 85 | password: ${{ secrets.GITHUB_TOKEN }} 86 | 87 | - name: Build (and push) 88 | uses: docker/build-push-action@v5 89 | with: 90 | context: . 91 | tags: ghcr.io/ciffelia/koe:git-${{ github.sha }} 92 | build-args: | 93 | SENTRY_RELEASE=${{ github.sha }} 94 | cache-from: type=gha 95 | cache-to: type=gha,mode=max 96 | push: ${{ github.ref == 'refs/heads/main' }} 97 | 98 | sentry-release: 99 | if: github.ref == 'refs/heads/main' 100 | needs: [docker-build-push] 101 | runs-on: ubuntu-22.04 102 | steps: 103 | - uses: actions/checkout@v4 104 | 105 | - name: Create Sentry release 106 | uses: getsentry/action-release@v1 107 | env: 108 | SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} 109 | SENTRY_ORG: ${{ secrets.SENTRY_ORG }} 110 | SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} 111 | with: 112 | environment: production 113 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | bump: 7 | description: Increment major, minor, or patch version 8 | type: choice 9 | required: true 10 | default: auto 11 | options: 12 | - auto 13 | - major 14 | - minor 15 | - patch 16 | 17 | permissions: 18 | contents: write 19 | packages: write 20 | 21 | concurrency: ${{ github.workflow }} 22 | 23 | jobs: 24 | create-release: 25 | runs-on: ubuntu-22.04 26 | steps: 27 | - name: Make sure that the container image is built 28 | run: skopeo inspect 'docker://ghcr.io/ciffelia/koe:git-${{ github.sha }}' 29 | 30 | - uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 0 33 | 34 | - uses: actions/setup-node@v4 35 | with: 36 | node-version: 18 37 | 38 | - name: Setup devtools 39 | run: yarn install --immutable 40 | working-directory: ./devtools 41 | 42 | - name: Configure git author 43 | run: | 44 | git config user.name 'github-actions' 45 | git config user.email '41898282+github-actions[bot]@users.noreply.github.com' 46 | 47 | - name: Create release 48 | run: | 49 | echo 'Creating ${{ inputs.bump }} release' 50 | 51 | if [ '${{ inputs.bump }}' == 'auto' ]; then 52 | yarn run create-release 53 | else 54 | yarn run create-release '${{ inputs.bump }}' 55 | fi 56 | working-directory: ./devtools 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - name: Generate Docker tags 61 | id: generate-docker-tags 62 | run: | 63 | tags="$(yarn run generate-docker-tags)" 64 | echo "$tags" 65 | echo "tags=$tags" >> $GITHUB_OUTPUT 66 | working-directory: ./devtools 67 | 68 | - name: Login to GitHub Container Registry 69 | uses: docker/login-action@v3 70 | with: 71 | registry: ghcr.io 72 | username: ${{ github.repository_owner }} 73 | password: ${{ secrets.GITHUB_TOKEN }} 74 | 75 | - name: Create Docker tags 76 | run: | 77 | for tag in ${{ steps.generate-docker-tags.outputs.tags }} 78 | do 79 | skopeo copy --all 'docker://ghcr.io/ciffelia/koe:git-${{ github.sha }}' "docker://ghcr.io/ciffelia/koe:$tag" 80 | done 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust build output 2 | /target 3 | 4 | # Config files 5 | /deployment_dev 6 | 7 | # CLion 8 | /.idea 9 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aead" 22 | version = "0.4.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" 25 | dependencies = [ 26 | "generic-array", 27 | "rand_core", 28 | ] 29 | 30 | [[package]] 31 | name = "aho-corasick" 32 | version = "1.1.3" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 35 | dependencies = [ 36 | "memchr", 37 | ] 38 | 39 | [[package]] 40 | name = "android-tzdata" 41 | version = "0.1.1" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 44 | 45 | [[package]] 46 | name = "android_system_properties" 47 | version = "0.1.5" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 50 | dependencies = [ 51 | "libc", 52 | ] 53 | 54 | [[package]] 55 | name = "anyhow" 56 | version = "1.0.86" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 59 | dependencies = [ 60 | "backtrace", 61 | ] 62 | 63 | [[package]] 64 | name = "arrayvec" 65 | version = "0.7.4" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 68 | 69 | [[package]] 70 | name = "async-trait" 71 | version = "0.1.74" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" 74 | dependencies = [ 75 | "proc-macro2", 76 | "quote", 77 | "syn 2.0.48", 78 | ] 79 | 80 | [[package]] 81 | name = "async-tungstenite" 82 | version = "0.17.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" 85 | dependencies = [ 86 | "futures-io", 87 | "futures-util", 88 | "log", 89 | "native-tls", 90 | "pin-project-lite", 91 | "tokio", 92 | "tokio-native-tls", 93 | "tungstenite", 94 | ] 95 | 96 | [[package]] 97 | name = "audiopus" 98 | version = "0.3.0-rc.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "ab55eb0e56d7c6de3d59f544e5db122d7725ec33be6a276ee8241f3be6473955" 101 | dependencies = [ 102 | "audiopus_sys", 103 | ] 104 | 105 | [[package]] 106 | name = "audiopus_sys" 107 | version = "0.2.2" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "62314a1546a2064e033665d658e88c620a62904be945f8147e6b16c3db9f8651" 110 | dependencies = [ 111 | "cmake", 112 | "log", 113 | "pkg-config", 114 | ] 115 | 116 | [[package]] 117 | name = "autocfg" 118 | version = "1.1.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 121 | 122 | [[package]] 123 | name = "backtrace" 124 | version = "0.3.69" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 127 | dependencies = [ 128 | "addr2line", 129 | "cc", 130 | "cfg-if", 131 | "libc", 132 | "miniz_oxide", 133 | "object", 134 | "rustc-demangle", 135 | ] 136 | 137 | [[package]] 138 | name = "base64" 139 | version = "0.13.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 142 | 143 | [[package]] 144 | name = "base64" 145 | version = "0.21.5" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" 148 | 149 | [[package]] 150 | name = "bitflags" 151 | version = "1.3.2" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 154 | 155 | [[package]] 156 | name = "bitflags" 157 | version = "2.4.1" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 160 | 161 | [[package]] 162 | name = "block-buffer" 163 | version = "0.10.4" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 166 | dependencies = [ 167 | "generic-array", 168 | ] 169 | 170 | [[package]] 171 | name = "bumpalo" 172 | version = "3.14.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 175 | 176 | [[package]] 177 | name = "bytemuck" 178 | version = "1.14.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" 181 | 182 | [[package]] 183 | name = "byteorder" 184 | version = "1.5.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 187 | 188 | [[package]] 189 | name = "bytes" 190 | version = "1.5.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 193 | 194 | [[package]] 195 | name = "cc" 196 | version = "1.0.83" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 199 | dependencies = [ 200 | "libc", 201 | ] 202 | 203 | [[package]] 204 | name = "cfg-if" 205 | version = "1.0.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 208 | 209 | [[package]] 210 | name = "chrono" 211 | version = "0.4.31" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" 214 | dependencies = [ 215 | "android-tzdata", 216 | "iana-time-zone", 217 | "js-sys", 218 | "num-traits 0.2.17", 219 | "serde", 220 | "wasm-bindgen", 221 | "windows-targets", 222 | ] 223 | 224 | [[package]] 225 | name = "cipher" 226 | version = "0.3.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 229 | dependencies = [ 230 | "generic-array", 231 | ] 232 | 233 | [[package]] 234 | name = "cmake" 235 | version = "0.1.50" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" 238 | dependencies = [ 239 | "cc", 240 | ] 241 | 242 | [[package]] 243 | name = "combine" 244 | version = "4.6.6" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" 247 | dependencies = [ 248 | "bytes", 249 | "futures-core", 250 | "memchr", 251 | "pin-project-lite", 252 | "tokio", 253 | "tokio-util", 254 | ] 255 | 256 | [[package]] 257 | name = "convert_case" 258 | version = "0.4.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 261 | 262 | [[package]] 263 | name = "core-foundation" 264 | version = "0.9.3" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 267 | dependencies = [ 268 | "core-foundation-sys", 269 | "libc", 270 | ] 271 | 272 | [[package]] 273 | name = "core-foundation-sys" 274 | version = "0.8.4" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 277 | 278 | [[package]] 279 | name = "cpufeatures" 280 | version = "0.2.11" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" 283 | dependencies = [ 284 | "libc", 285 | ] 286 | 287 | [[package]] 288 | name = "crc32fast" 289 | version = "1.3.2" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 292 | dependencies = [ 293 | "cfg-if", 294 | ] 295 | 296 | [[package]] 297 | name = "crossbeam-utils" 298 | version = "0.8.16" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 301 | dependencies = [ 302 | "cfg-if", 303 | ] 304 | 305 | [[package]] 306 | name = "crypto-common" 307 | version = "0.1.6" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 310 | dependencies = [ 311 | "generic-array", 312 | "typenum", 313 | ] 314 | 315 | [[package]] 316 | name = "dashmap" 317 | version = "5.5.3" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 320 | dependencies = [ 321 | "cfg-if", 322 | "hashbrown 0.14.2", 323 | "lock_api", 324 | "once_cell", 325 | "parking_lot_core", 326 | "serde", 327 | ] 328 | 329 | [[package]] 330 | name = "debugid" 331 | version = "0.8.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" 334 | dependencies = [ 335 | "serde", 336 | "uuid 1.5.0", 337 | ] 338 | 339 | [[package]] 340 | name = "deranged" 341 | version = "0.3.9" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" 344 | dependencies = [ 345 | "powerfmt", 346 | "serde", 347 | ] 348 | 349 | [[package]] 350 | name = "derivative" 351 | version = "2.2.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 354 | dependencies = [ 355 | "proc-macro2", 356 | "quote", 357 | "syn 1.0.109", 358 | ] 359 | 360 | [[package]] 361 | name = "derive_more" 362 | version = "0.99.17" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 365 | dependencies = [ 366 | "convert_case", 367 | "proc-macro2", 368 | "quote", 369 | "rustc_version", 370 | "syn 1.0.109", 371 | ] 372 | 373 | [[package]] 374 | name = "digest" 375 | version = "0.10.7" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 378 | dependencies = [ 379 | "block-buffer", 380 | "crypto-common", 381 | ] 382 | 383 | [[package]] 384 | name = "discord-md" 385 | version = "3.0.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "bf734867cf313d9ffed7677af003ce6779ce77a80d4f8a9101959c97df3ef96b" 388 | dependencies = [ 389 | "derive_more", 390 | "nom", 391 | ] 392 | 393 | [[package]] 394 | name = "discortp" 395 | version = "0.4.0" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "fb66017646a48220b5ea30d63ac18bb5952f647f1a41ed755880895125d26972" 398 | dependencies = [ 399 | "pnet_macros", 400 | "pnet_macros_support", 401 | ] 402 | 403 | [[package]] 404 | name = "ecs-logger" 405 | version = "1.1.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "64e5afecb521e3e32aa50153f4e2940a123ee8e70a67e41951af235a98564d11" 408 | dependencies = [ 409 | "chrono", 410 | "env_logger", 411 | "log", 412 | "serde", 413 | "serde_json", 414 | "thiserror", 415 | ] 416 | 417 | [[package]] 418 | name = "encoding_rs" 419 | version = "0.8.33" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 422 | dependencies = [ 423 | "cfg-if", 424 | ] 425 | 426 | [[package]] 427 | name = "enum_primitive" 428 | version = "0.1.1" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" 431 | dependencies = [ 432 | "num-traits 0.1.43", 433 | ] 434 | 435 | [[package]] 436 | name = "env_logger" 437 | version = "0.10.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" 440 | dependencies = [ 441 | "log", 442 | ] 443 | 444 | [[package]] 445 | name = "equivalent" 446 | version = "1.0.1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 449 | 450 | [[package]] 451 | name = "errno" 452 | version = "0.3.6" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" 455 | dependencies = [ 456 | "libc", 457 | "windows-sys", 458 | ] 459 | 460 | [[package]] 461 | name = "fastrand" 462 | version = "2.0.1" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 465 | 466 | [[package]] 467 | name = "findshlibs" 468 | version = "0.10.2" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" 471 | dependencies = [ 472 | "cc", 473 | "lazy_static", 474 | "libc", 475 | "winapi", 476 | ] 477 | 478 | [[package]] 479 | name = "flate2" 480 | version = "1.0.28" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 483 | dependencies = [ 484 | "crc32fast", 485 | "miniz_oxide", 486 | ] 487 | 488 | [[package]] 489 | name = "flume" 490 | version = "0.10.14" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" 493 | dependencies = [ 494 | "futures-core", 495 | "futures-sink", 496 | "nanorand", 497 | "pin-project", 498 | "spin", 499 | ] 500 | 501 | [[package]] 502 | name = "fnv" 503 | version = "1.0.7" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 506 | 507 | [[package]] 508 | name = "foreign-types" 509 | version = "0.3.2" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 512 | dependencies = [ 513 | "foreign-types-shared", 514 | ] 515 | 516 | [[package]] 517 | name = "foreign-types-shared" 518 | version = "0.1.1" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 521 | 522 | [[package]] 523 | name = "form_urlencoded" 524 | version = "1.2.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 527 | dependencies = [ 528 | "percent-encoding", 529 | ] 530 | 531 | [[package]] 532 | name = "futures" 533 | version = "0.3.29" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" 536 | dependencies = [ 537 | "futures-channel", 538 | "futures-core", 539 | "futures-executor", 540 | "futures-io", 541 | "futures-sink", 542 | "futures-task", 543 | "futures-util", 544 | ] 545 | 546 | [[package]] 547 | name = "futures-channel" 548 | version = "0.3.29" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" 551 | dependencies = [ 552 | "futures-core", 553 | "futures-sink", 554 | ] 555 | 556 | [[package]] 557 | name = "futures-core" 558 | version = "0.3.29" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" 561 | 562 | [[package]] 563 | name = "futures-executor" 564 | version = "0.3.29" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" 567 | dependencies = [ 568 | "futures-core", 569 | "futures-task", 570 | "futures-util", 571 | ] 572 | 573 | [[package]] 574 | name = "futures-io" 575 | version = "0.3.29" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" 578 | 579 | [[package]] 580 | name = "futures-macro" 581 | version = "0.3.29" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" 584 | dependencies = [ 585 | "proc-macro2", 586 | "quote", 587 | "syn 2.0.48", 588 | ] 589 | 590 | [[package]] 591 | name = "futures-sink" 592 | version = "0.3.29" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" 595 | 596 | [[package]] 597 | name = "futures-task" 598 | version = "0.3.29" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" 601 | 602 | [[package]] 603 | name = "futures-util" 604 | version = "0.3.29" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" 607 | dependencies = [ 608 | "futures-channel", 609 | "futures-core", 610 | "futures-io", 611 | "futures-macro", 612 | "futures-sink", 613 | "futures-task", 614 | "memchr", 615 | "pin-project-lite", 616 | "pin-utils", 617 | "slab", 618 | ] 619 | 620 | [[package]] 621 | name = "generator" 622 | version = "0.7.5" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" 625 | dependencies = [ 626 | "cc", 627 | "libc", 628 | "log", 629 | "rustversion", 630 | "windows", 631 | ] 632 | 633 | [[package]] 634 | name = "generic-array" 635 | version = "0.14.7" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 638 | dependencies = [ 639 | "typenum", 640 | "version_check", 641 | ] 642 | 643 | [[package]] 644 | name = "getrandom" 645 | version = "0.2.11" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 648 | dependencies = [ 649 | "cfg-if", 650 | "js-sys", 651 | "libc", 652 | "wasi", 653 | "wasm-bindgen", 654 | ] 655 | 656 | [[package]] 657 | name = "gimli" 658 | version = "0.28.0" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 661 | 662 | [[package]] 663 | name = "h2" 664 | version = "0.3.21" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" 667 | dependencies = [ 668 | "bytes", 669 | "fnv", 670 | "futures-core", 671 | "futures-sink", 672 | "futures-util", 673 | "http", 674 | "indexmap 1.9.3", 675 | "slab", 676 | "tokio", 677 | "tokio-util", 678 | "tracing", 679 | ] 680 | 681 | [[package]] 682 | name = "hashbrown" 683 | version = "0.12.3" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 686 | 687 | [[package]] 688 | name = "hashbrown" 689 | version = "0.14.2" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" 692 | 693 | [[package]] 694 | name = "hermit-abi" 695 | version = "0.3.3" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 698 | 699 | [[package]] 700 | name = "hex" 701 | version = "0.4.3" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 704 | 705 | [[package]] 706 | name = "hostname" 707 | version = "0.3.1" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" 710 | dependencies = [ 711 | "libc", 712 | "match_cfg", 713 | "winapi", 714 | ] 715 | 716 | [[package]] 717 | name = "http" 718 | version = "0.2.10" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150" 721 | dependencies = [ 722 | "bytes", 723 | "fnv", 724 | "itoa", 725 | ] 726 | 727 | [[package]] 728 | name = "http-body" 729 | version = "0.4.5" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 732 | dependencies = [ 733 | "bytes", 734 | "http", 735 | "pin-project-lite", 736 | ] 737 | 738 | [[package]] 739 | name = "httparse" 740 | version = "1.8.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 743 | 744 | [[package]] 745 | name = "httpdate" 746 | version = "1.0.3" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 749 | 750 | [[package]] 751 | name = "hyper" 752 | version = "0.14.27" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" 755 | dependencies = [ 756 | "bytes", 757 | "futures-channel", 758 | "futures-core", 759 | "futures-util", 760 | "h2", 761 | "http", 762 | "http-body", 763 | "httparse", 764 | "httpdate", 765 | "itoa", 766 | "pin-project-lite", 767 | "socket2 0.4.10", 768 | "tokio", 769 | "tower-service", 770 | "tracing", 771 | "want", 772 | ] 773 | 774 | [[package]] 775 | name = "hyper-tls" 776 | version = "0.5.0" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 779 | dependencies = [ 780 | "bytes", 781 | "hyper", 782 | "native-tls", 783 | "tokio", 784 | "tokio-native-tls", 785 | ] 786 | 787 | [[package]] 788 | name = "iana-time-zone" 789 | version = "0.1.58" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" 792 | dependencies = [ 793 | "android_system_properties", 794 | "core-foundation-sys", 795 | "iana-time-zone-haiku", 796 | "js-sys", 797 | "wasm-bindgen", 798 | "windows-core", 799 | ] 800 | 801 | [[package]] 802 | name = "iana-time-zone-haiku" 803 | version = "0.1.2" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 806 | dependencies = [ 807 | "cc", 808 | ] 809 | 810 | [[package]] 811 | name = "idna" 812 | version = "0.4.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 815 | dependencies = [ 816 | "unicode-bidi", 817 | "unicode-normalization", 818 | ] 819 | 820 | [[package]] 821 | name = "indexmap" 822 | version = "1.9.3" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 825 | dependencies = [ 826 | "autocfg", 827 | "hashbrown 0.12.3", 828 | ] 829 | 830 | [[package]] 831 | name = "indexmap" 832 | version = "2.1.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 835 | dependencies = [ 836 | "equivalent", 837 | "hashbrown 0.14.2", 838 | ] 839 | 840 | [[package]] 841 | name = "ipnet" 842 | version = "2.9.0" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 845 | 846 | [[package]] 847 | name = "itoa" 848 | version = "1.0.9" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 851 | 852 | [[package]] 853 | name = "js-sys" 854 | version = "0.3.65" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" 857 | dependencies = [ 858 | "wasm-bindgen", 859 | ] 860 | 861 | [[package]] 862 | name = "koe" 863 | version = "0.1.0" 864 | dependencies = [ 865 | "aho-corasick", 866 | "anyhow", 867 | "dashmap", 868 | "discord-md", 869 | "ecs-logger", 870 | "koe-audio", 871 | "koe-call", 872 | "koe-config", 873 | "koe-db", 874 | "koe-speech", 875 | "log", 876 | "once_cell", 877 | "rand", 878 | "regex", 879 | "sentry", 880 | "serenity", 881 | "songbird", 882 | "tokio", 883 | ] 884 | 885 | [[package]] 886 | name = "koe-audio" 887 | version = "0.1.0" 888 | dependencies = [ 889 | "anyhow", 890 | "log", 891 | "tokio", 892 | ] 893 | 894 | [[package]] 895 | name = "koe-call" 896 | version = "0.1.0" 897 | dependencies = [ 898 | "anyhow", 899 | "serenity", 900 | "songbird", 901 | "tokio", 902 | ] 903 | 904 | [[package]] 905 | name = "koe-config" 906 | version = "0.1.0" 907 | dependencies = [ 908 | "anyhow", 909 | "serde", 910 | "serde_yaml", 911 | "tokio", 912 | ] 913 | 914 | [[package]] 915 | name = "koe-db" 916 | version = "0.1.0" 917 | dependencies = [ 918 | "anyhow", 919 | "redis", 920 | ] 921 | 922 | [[package]] 923 | name = "koe-speech" 924 | version = "0.1.0" 925 | dependencies = [ 926 | "anyhow", 927 | "koe-audio", 928 | "reqwest", 929 | "serde", 930 | ] 931 | 932 | [[package]] 933 | name = "lazy_static" 934 | version = "1.4.0" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 937 | 938 | [[package]] 939 | name = "libc" 940 | version = "0.2.150" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" 943 | 944 | [[package]] 945 | name = "linux-raw-sys" 946 | version = "0.4.11" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" 949 | 950 | [[package]] 951 | name = "lock_api" 952 | version = "0.4.11" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 955 | dependencies = [ 956 | "autocfg", 957 | "scopeguard", 958 | ] 959 | 960 | [[package]] 961 | name = "log" 962 | version = "0.4.20" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 965 | 966 | [[package]] 967 | name = "loom" 968 | version = "0.5.6" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" 971 | dependencies = [ 972 | "cfg-if", 973 | "generator", 974 | "scoped-tls", 975 | "serde", 976 | "serde_json", 977 | "tracing", 978 | "tracing-subscriber", 979 | ] 980 | 981 | [[package]] 982 | name = "match_cfg" 983 | version = "0.1.0" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" 986 | 987 | [[package]] 988 | name = "matchers" 989 | version = "0.1.0" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 992 | dependencies = [ 993 | "regex-automata 0.1.10", 994 | ] 995 | 996 | [[package]] 997 | name = "memchr" 998 | version = "2.6.4" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 1001 | 1002 | [[package]] 1003 | name = "mime" 1004 | version = "0.3.17" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1007 | 1008 | [[package]] 1009 | name = "mime_guess" 1010 | version = "2.0.4" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 1013 | dependencies = [ 1014 | "mime", 1015 | "unicase", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "minimal-lexical" 1020 | version = "0.2.1" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1023 | 1024 | [[package]] 1025 | name = "miniz_oxide" 1026 | version = "0.7.1" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 1029 | dependencies = [ 1030 | "adler", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "mio" 1035 | version = "0.8.9" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" 1038 | dependencies = [ 1039 | "libc", 1040 | "wasi", 1041 | "windows-sys", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "nanorand" 1046 | version = "0.7.0" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 1049 | dependencies = [ 1050 | "getrandom", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "native-tls" 1055 | version = "0.2.11" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 1058 | dependencies = [ 1059 | "lazy_static", 1060 | "libc", 1061 | "log", 1062 | "openssl", 1063 | "openssl-probe", 1064 | "openssl-sys", 1065 | "schannel", 1066 | "security-framework", 1067 | "security-framework-sys", 1068 | "tempfile", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "nom" 1073 | version = "7.1.3" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1076 | dependencies = [ 1077 | "memchr", 1078 | "minimal-lexical", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "nu-ansi-term" 1083 | version = "0.46.0" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1086 | dependencies = [ 1087 | "overload", 1088 | "winapi", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "num-traits" 1093 | version = "0.1.43" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" 1096 | dependencies = [ 1097 | "num-traits 0.2.17", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "num-traits" 1102 | version = "0.2.17" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 1105 | dependencies = [ 1106 | "autocfg", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "num_cpus" 1111 | version = "1.16.0" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 1114 | dependencies = [ 1115 | "hermit-abi", 1116 | "libc", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "object" 1121 | version = "0.32.1" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 1124 | dependencies = [ 1125 | "memchr", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "once_cell" 1130 | version = "1.19.0" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1133 | 1134 | [[package]] 1135 | name = "opaque-debug" 1136 | version = "0.3.0" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 1139 | 1140 | [[package]] 1141 | name = "openssl" 1142 | version = "0.10.59" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" 1145 | dependencies = [ 1146 | "bitflags 2.4.1", 1147 | "cfg-if", 1148 | "foreign-types", 1149 | "libc", 1150 | "once_cell", 1151 | "openssl-macros", 1152 | "openssl-sys", 1153 | ] 1154 | 1155 | [[package]] 1156 | name = "openssl-macros" 1157 | version = "0.1.1" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1160 | dependencies = [ 1161 | "proc-macro2", 1162 | "quote", 1163 | "syn 2.0.48", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "openssl-probe" 1168 | version = "0.1.5" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1171 | 1172 | [[package]] 1173 | name = "openssl-sys" 1174 | version = "0.9.95" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" 1177 | dependencies = [ 1178 | "cc", 1179 | "libc", 1180 | "pkg-config", 1181 | "vcpkg", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "ordered-float" 1186 | version = "2.10.1" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" 1189 | dependencies = [ 1190 | "num-traits 0.2.17", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "os_info" 1195 | version = "3.7.0" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" 1198 | dependencies = [ 1199 | "log", 1200 | "serde", 1201 | "winapi", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "overload" 1206 | version = "0.1.1" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1209 | 1210 | [[package]] 1211 | name = "parking_lot" 1212 | version = "0.12.1" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1215 | dependencies = [ 1216 | "lock_api", 1217 | "parking_lot_core", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "parking_lot_core" 1222 | version = "0.9.9" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 1225 | dependencies = [ 1226 | "cfg-if", 1227 | "libc", 1228 | "redox_syscall", 1229 | "smallvec", 1230 | "windows-targets", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "percent-encoding" 1235 | version = "2.3.0" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 1238 | 1239 | [[package]] 1240 | name = "pin-project" 1241 | version = "1.1.3" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" 1244 | dependencies = [ 1245 | "pin-project-internal", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "pin-project-internal" 1250 | version = "1.1.3" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" 1253 | dependencies = [ 1254 | "proc-macro2", 1255 | "quote", 1256 | "syn 2.0.48", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "pin-project-lite" 1261 | version = "0.2.13" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 1264 | 1265 | [[package]] 1266 | name = "pin-utils" 1267 | version = "0.1.0" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1270 | 1271 | [[package]] 1272 | name = "pkg-config" 1273 | version = "0.3.27" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 1276 | 1277 | [[package]] 1278 | name = "pnet_base" 1279 | version = "0.28.0" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "25488cd551a753dcaaa6fffc9f69a7610a412dd8954425bf7ffad5f7d1156fb8" 1282 | 1283 | [[package]] 1284 | name = "pnet_macros" 1285 | version = "0.28.0" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "30490e0852e58402b8fae0d39897b08a24f493023a4d6cf56b2e30f31ed57548" 1288 | dependencies = [ 1289 | "proc-macro2", 1290 | "quote", 1291 | "regex", 1292 | "syn 1.0.109", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "pnet_macros_support" 1297 | version = "0.28.0" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "d4714e10f30cab023005adce048f2d30dd4ac4f093662abf2220855655ef8f90" 1300 | dependencies = [ 1301 | "pnet_base", 1302 | ] 1303 | 1304 | [[package]] 1305 | name = "poly1305" 1306 | version = "0.7.2" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" 1309 | dependencies = [ 1310 | "cpufeatures", 1311 | "opaque-debug", 1312 | "universal-hash", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "powerfmt" 1317 | version = "0.2.0" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1320 | 1321 | [[package]] 1322 | name = "ppv-lite86" 1323 | version = "0.2.17" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1326 | 1327 | [[package]] 1328 | name = "proc-macro2" 1329 | version = "1.0.75" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" 1332 | dependencies = [ 1333 | "unicode-ident", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "quote" 1338 | version = "1.0.35" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 1341 | dependencies = [ 1342 | "proc-macro2", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "rand" 1347 | version = "0.8.5" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1350 | dependencies = [ 1351 | "libc", 1352 | "rand_chacha", 1353 | "rand_core", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "rand_chacha" 1358 | version = "0.3.1" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1361 | dependencies = [ 1362 | "ppv-lite86", 1363 | "rand_core", 1364 | ] 1365 | 1366 | [[package]] 1367 | name = "rand_core" 1368 | version = "0.6.4" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1371 | dependencies = [ 1372 | "getrandom", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "redis" 1377 | version = "0.23.3" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" 1380 | dependencies = [ 1381 | "async-trait", 1382 | "bytes", 1383 | "combine", 1384 | "futures-util", 1385 | "itoa", 1386 | "percent-encoding", 1387 | "pin-project-lite", 1388 | "ryu", 1389 | "tokio", 1390 | "tokio-util", 1391 | "url", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "redox_syscall" 1396 | version = "0.4.1" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1399 | dependencies = [ 1400 | "bitflags 1.3.2", 1401 | ] 1402 | 1403 | [[package]] 1404 | name = "regex" 1405 | version = "1.10.4" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 1408 | dependencies = [ 1409 | "aho-corasick", 1410 | "memchr", 1411 | "regex-automata 0.4.5", 1412 | "regex-syntax 0.8.2", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "regex-automata" 1417 | version = "0.1.10" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1420 | dependencies = [ 1421 | "regex-syntax 0.6.29", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "regex-automata" 1426 | version = "0.4.5" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" 1429 | dependencies = [ 1430 | "aho-corasick", 1431 | "memchr", 1432 | "regex-syntax 0.8.2", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "regex-syntax" 1437 | version = "0.6.29" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 1440 | 1441 | [[package]] 1442 | name = "regex-syntax" 1443 | version = "0.8.2" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 1446 | 1447 | [[package]] 1448 | name = "reqwest" 1449 | version = "0.11.22" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" 1452 | dependencies = [ 1453 | "base64 0.21.5", 1454 | "bytes", 1455 | "encoding_rs", 1456 | "futures-core", 1457 | "futures-util", 1458 | "h2", 1459 | "http", 1460 | "http-body", 1461 | "hyper", 1462 | "hyper-tls", 1463 | "ipnet", 1464 | "js-sys", 1465 | "log", 1466 | "mime", 1467 | "mime_guess", 1468 | "native-tls", 1469 | "once_cell", 1470 | "percent-encoding", 1471 | "pin-project-lite", 1472 | "serde", 1473 | "serde_json", 1474 | "serde_urlencoded", 1475 | "system-configuration", 1476 | "tokio", 1477 | "tokio-native-tls", 1478 | "tokio-util", 1479 | "tower-service", 1480 | "url", 1481 | "wasm-bindgen", 1482 | "wasm-bindgen-futures", 1483 | "wasm-streams", 1484 | "web-sys", 1485 | "winreg", 1486 | ] 1487 | 1488 | [[package]] 1489 | name = "rustc-demangle" 1490 | version = "0.1.23" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1493 | 1494 | [[package]] 1495 | name = "rustc_version" 1496 | version = "0.4.0" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1499 | dependencies = [ 1500 | "semver", 1501 | ] 1502 | 1503 | [[package]] 1504 | name = "rustix" 1505 | version = "0.38.21" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" 1508 | dependencies = [ 1509 | "bitflags 2.4.1", 1510 | "errno", 1511 | "libc", 1512 | "linux-raw-sys", 1513 | "windows-sys", 1514 | ] 1515 | 1516 | [[package]] 1517 | name = "rustversion" 1518 | version = "1.0.14" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 1521 | 1522 | [[package]] 1523 | name = "ryu" 1524 | version = "1.0.15" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 1527 | 1528 | [[package]] 1529 | name = "salsa20" 1530 | version = "0.9.0" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "0c0fbb5f676da676c260ba276a8f43a8dc67cf02d1438423aeb1c677a7212686" 1533 | dependencies = [ 1534 | "cipher", 1535 | "zeroize", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "schannel" 1540 | version = "0.1.22" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" 1543 | dependencies = [ 1544 | "windows-sys", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "scoped-tls" 1549 | version = "1.0.1" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1552 | 1553 | [[package]] 1554 | name = "scopeguard" 1555 | version = "1.2.0" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1558 | 1559 | [[package]] 1560 | name = "security-framework" 1561 | version = "2.9.2" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" 1564 | dependencies = [ 1565 | "bitflags 1.3.2", 1566 | "core-foundation", 1567 | "core-foundation-sys", 1568 | "libc", 1569 | "security-framework-sys", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "security-framework-sys" 1574 | version = "2.9.1" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" 1577 | dependencies = [ 1578 | "core-foundation-sys", 1579 | "libc", 1580 | ] 1581 | 1582 | [[package]] 1583 | name = "semver" 1584 | version = "1.0.20" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" 1587 | 1588 | [[package]] 1589 | name = "sentry" 1590 | version = "0.31.8" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "6ce4b57f1b521f674df7a1d200be8ff5d74e3712020ee25b553146657b5377d5" 1593 | dependencies = [ 1594 | "httpdate", 1595 | "native-tls", 1596 | "reqwest", 1597 | "sentry-anyhow", 1598 | "sentry-backtrace", 1599 | "sentry-contexts", 1600 | "sentry-core", 1601 | "sentry-debug-images", 1602 | "sentry-panic", 1603 | "sentry-tracing", 1604 | "tokio", 1605 | "ureq", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "sentry-anyhow" 1610 | version = "0.31.8" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "8868ca6e513f7a80b394b7e0f4b6071afeebb69e62b5e4aafe37b45e431fac8b" 1613 | dependencies = [ 1614 | "anyhow", 1615 | "sentry-backtrace", 1616 | "sentry-core", 1617 | ] 1618 | 1619 | [[package]] 1620 | name = "sentry-backtrace" 1621 | version = "0.31.8" 1622 | source = "registry+https://github.com/rust-lang/crates.io-index" 1623 | checksum = "58cc8d4e04a73de8f718dc703943666d03f25d3e9e4d0fb271ca0b8c76dfa00e" 1624 | dependencies = [ 1625 | "backtrace", 1626 | "once_cell", 1627 | "regex", 1628 | "sentry-core", 1629 | ] 1630 | 1631 | [[package]] 1632 | name = "sentry-contexts" 1633 | version = "0.31.8" 1634 | source = "registry+https://github.com/rust-lang/crates.io-index" 1635 | checksum = "6436c1bad22cdeb02179ea8ef116ffc217797c028927def303bc593d9320c0d1" 1636 | dependencies = [ 1637 | "hostname", 1638 | "libc", 1639 | "os_info", 1640 | "rustc_version", 1641 | "sentry-core", 1642 | "uname", 1643 | ] 1644 | 1645 | [[package]] 1646 | name = "sentry-core" 1647 | version = "0.31.8" 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" 1649 | checksum = "901f761681f97db3db836ef9e094acdd8756c40215326c194201941947164ef1" 1650 | dependencies = [ 1651 | "once_cell", 1652 | "rand", 1653 | "sentry-types", 1654 | "serde", 1655 | "serde_json", 1656 | ] 1657 | 1658 | [[package]] 1659 | name = "sentry-debug-images" 1660 | version = "0.31.8" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "afdb263e73d22f39946f6022ed455b7561b22ff5553aca9be3c6a047fa39c328" 1663 | dependencies = [ 1664 | "findshlibs", 1665 | "once_cell", 1666 | "sentry-core", 1667 | ] 1668 | 1669 | [[package]] 1670 | name = "sentry-panic" 1671 | version = "0.31.8" 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" 1673 | checksum = "74fbf1c163f8b6a9d05912e1b272afa27c652e8b47ea60cb9a57ad5e481eea99" 1674 | dependencies = [ 1675 | "sentry-backtrace", 1676 | "sentry-core", 1677 | ] 1678 | 1679 | [[package]] 1680 | name = "sentry-tracing" 1681 | version = "0.31.8" 1682 | source = "registry+https://github.com/rust-lang/crates.io-index" 1683 | checksum = "82eabcab0a047040befd44599a1da73d3adb228ff53b5ed9795ae04535577704" 1684 | dependencies = [ 1685 | "sentry-backtrace", 1686 | "sentry-core", 1687 | "tracing-core", 1688 | "tracing-subscriber", 1689 | ] 1690 | 1691 | [[package]] 1692 | name = "sentry-types" 1693 | version = "0.31.8" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "da956cca56e0101998c8688bc65ce1a96f00673a0e58e663664023d4c7911e82" 1696 | dependencies = [ 1697 | "debugid", 1698 | "hex", 1699 | "rand", 1700 | "serde", 1701 | "serde_json", 1702 | "thiserror", 1703 | "time", 1704 | "url", 1705 | "uuid 1.5.0", 1706 | ] 1707 | 1708 | [[package]] 1709 | name = "serde" 1710 | version = "1.0.203" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 1713 | dependencies = [ 1714 | "serde_derive", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "serde-value" 1719 | version = "0.7.0" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" 1722 | dependencies = [ 1723 | "ordered-float", 1724 | "serde", 1725 | ] 1726 | 1727 | [[package]] 1728 | name = "serde_derive" 1729 | version = "1.0.203" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 1732 | dependencies = [ 1733 | "proc-macro2", 1734 | "quote", 1735 | "syn 2.0.48", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "serde_json" 1740 | version = "1.0.108" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 1743 | dependencies = [ 1744 | "indexmap 2.1.0", 1745 | "itoa", 1746 | "ryu", 1747 | "serde", 1748 | ] 1749 | 1750 | [[package]] 1751 | name = "serde_repr" 1752 | version = "0.1.17" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" 1755 | dependencies = [ 1756 | "proc-macro2", 1757 | "quote", 1758 | "syn 2.0.48", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "serde_urlencoded" 1763 | version = "0.7.1" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1766 | dependencies = [ 1767 | "form_urlencoded", 1768 | "itoa", 1769 | "ryu", 1770 | "serde", 1771 | ] 1772 | 1773 | [[package]] 1774 | name = "serde_yaml" 1775 | version = "0.9.27" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" 1778 | dependencies = [ 1779 | "indexmap 2.1.0", 1780 | "itoa", 1781 | "ryu", 1782 | "serde", 1783 | "unsafe-libyaml", 1784 | ] 1785 | 1786 | [[package]] 1787 | name = "serenity" 1788 | version = "0.11.7" 1789 | source = "registry+https://github.com/rust-lang/crates.io-index" 1790 | checksum = "7a7a89cef23483fc9d4caf2df41e6d3928e18aada84c56abd237439d929622c6" 1791 | dependencies = [ 1792 | "async-trait", 1793 | "async-tungstenite", 1794 | "base64 0.21.5", 1795 | "bitflags 1.3.2", 1796 | "bytes", 1797 | "cfg-if", 1798 | "dashmap", 1799 | "flate2", 1800 | "futures", 1801 | "mime", 1802 | "mime_guess", 1803 | "parking_lot", 1804 | "percent-encoding", 1805 | "reqwest", 1806 | "serde", 1807 | "serde-value", 1808 | "serde_json", 1809 | "time", 1810 | "tokio", 1811 | "tracing", 1812 | "typemap_rev", 1813 | "url", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "serenity-voice-model" 1818 | version = "0.1.1" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "8be3aec8849ca2fde1e8a5dfbed96fbd68e9b5f4283fbe277d8694ce811d4952" 1821 | dependencies = [ 1822 | "bitflags 1.3.2", 1823 | "enum_primitive", 1824 | "serde", 1825 | "serde_json", 1826 | "serde_repr", 1827 | ] 1828 | 1829 | [[package]] 1830 | name = "sha-1" 1831 | version = "0.10.1" 1832 | source = "registry+https://github.com/rust-lang/crates.io-index" 1833 | checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" 1834 | dependencies = [ 1835 | "cfg-if", 1836 | "cpufeatures", 1837 | "digest", 1838 | ] 1839 | 1840 | [[package]] 1841 | name = "sharded-slab" 1842 | version = "0.1.7" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1845 | dependencies = [ 1846 | "lazy_static", 1847 | ] 1848 | 1849 | [[package]] 1850 | name = "signal-hook-registry" 1851 | version = "1.4.1" 1852 | source = "registry+https://github.com/rust-lang/crates.io-index" 1853 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1854 | dependencies = [ 1855 | "libc", 1856 | ] 1857 | 1858 | [[package]] 1859 | name = "slab" 1860 | version = "0.4.9" 1861 | source = "registry+https://github.com/rust-lang/crates.io-index" 1862 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1863 | dependencies = [ 1864 | "autocfg", 1865 | ] 1866 | 1867 | [[package]] 1868 | name = "smallvec" 1869 | version = "1.11.2" 1870 | source = "registry+https://github.com/rust-lang/crates.io-index" 1871 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" 1872 | 1873 | [[package]] 1874 | name = "socket2" 1875 | version = "0.4.10" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" 1878 | dependencies = [ 1879 | "libc", 1880 | "winapi", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "socket2" 1885 | version = "0.5.5" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 1888 | dependencies = [ 1889 | "libc", 1890 | "windows-sys", 1891 | ] 1892 | 1893 | [[package]] 1894 | name = "songbird" 1895 | version = "0.3.2" 1896 | source = "registry+https://github.com/rust-lang/crates.io-index" 1897 | checksum = "32f686a0fd771939de1da3e43cee45169fafe1595770b94680572cf18bdef288" 1898 | dependencies = [ 1899 | "async-trait", 1900 | "async-tungstenite", 1901 | "audiopus", 1902 | "byteorder", 1903 | "dashmap", 1904 | "derivative", 1905 | "discortp", 1906 | "flume", 1907 | "futures", 1908 | "parking_lot", 1909 | "pin-project", 1910 | "rand", 1911 | "serde", 1912 | "serde_json", 1913 | "serenity", 1914 | "serenity-voice-model", 1915 | "streamcatcher", 1916 | "symphonia-core", 1917 | "tokio", 1918 | "tracing", 1919 | "tracing-futures", 1920 | "typemap_rev", 1921 | "url", 1922 | "uuid 0.8.2", 1923 | "xsalsa20poly1305", 1924 | ] 1925 | 1926 | [[package]] 1927 | name = "spin" 1928 | version = "0.9.8" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1931 | dependencies = [ 1932 | "lock_api", 1933 | ] 1934 | 1935 | [[package]] 1936 | name = "streamcatcher" 1937 | version = "1.0.1" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "71664755c349abb0758fda6218fb2d2391ca2a73f9302c03b145491db4fcea29" 1940 | dependencies = [ 1941 | "crossbeam-utils", 1942 | "futures-util", 1943 | "loom", 1944 | ] 1945 | 1946 | [[package]] 1947 | name = "subtle" 1948 | version = "2.4.1" 1949 | source = "registry+https://github.com/rust-lang/crates.io-index" 1950 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1951 | 1952 | [[package]] 1953 | name = "symphonia-core" 1954 | version = "0.5.3" 1955 | source = "registry+https://github.com/rust-lang/crates.io-index" 1956 | checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142" 1957 | dependencies = [ 1958 | "arrayvec", 1959 | "bitflags 1.3.2", 1960 | "bytemuck", 1961 | "lazy_static", 1962 | "log", 1963 | ] 1964 | 1965 | [[package]] 1966 | name = "syn" 1967 | version = "1.0.109" 1968 | source = "registry+https://github.com/rust-lang/crates.io-index" 1969 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1970 | dependencies = [ 1971 | "proc-macro2", 1972 | "quote", 1973 | "unicode-ident", 1974 | ] 1975 | 1976 | [[package]] 1977 | name = "syn" 1978 | version = "2.0.48" 1979 | source = "registry+https://github.com/rust-lang/crates.io-index" 1980 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 1981 | dependencies = [ 1982 | "proc-macro2", 1983 | "quote", 1984 | "unicode-ident", 1985 | ] 1986 | 1987 | [[package]] 1988 | name = "system-configuration" 1989 | version = "0.5.1" 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" 1991 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1992 | dependencies = [ 1993 | "bitflags 1.3.2", 1994 | "core-foundation", 1995 | "system-configuration-sys", 1996 | ] 1997 | 1998 | [[package]] 1999 | name = "system-configuration-sys" 2000 | version = "0.5.0" 2001 | source = "registry+https://github.com/rust-lang/crates.io-index" 2002 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 2003 | dependencies = [ 2004 | "core-foundation-sys", 2005 | "libc", 2006 | ] 2007 | 2008 | [[package]] 2009 | name = "tempfile" 2010 | version = "3.8.1" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" 2013 | dependencies = [ 2014 | "cfg-if", 2015 | "fastrand", 2016 | "redox_syscall", 2017 | "rustix", 2018 | "windows-sys", 2019 | ] 2020 | 2021 | [[package]] 2022 | name = "thiserror" 2023 | version = "1.0.50" 2024 | source = "registry+https://github.com/rust-lang/crates.io-index" 2025 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 2026 | dependencies = [ 2027 | "thiserror-impl", 2028 | ] 2029 | 2030 | [[package]] 2031 | name = "thiserror-impl" 2032 | version = "1.0.50" 2033 | source = "registry+https://github.com/rust-lang/crates.io-index" 2034 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 2035 | dependencies = [ 2036 | "proc-macro2", 2037 | "quote", 2038 | "syn 2.0.48", 2039 | ] 2040 | 2041 | [[package]] 2042 | name = "thread_local" 2043 | version = "1.1.7" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 2046 | dependencies = [ 2047 | "cfg-if", 2048 | "once_cell", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "time" 2053 | version = "0.3.30" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" 2056 | dependencies = [ 2057 | "deranged", 2058 | "itoa", 2059 | "powerfmt", 2060 | "serde", 2061 | "time-core", 2062 | "time-macros", 2063 | ] 2064 | 2065 | [[package]] 2066 | name = "time-core" 2067 | version = "0.1.2" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 2070 | 2071 | [[package]] 2072 | name = "time-macros" 2073 | version = "0.2.15" 2074 | source = "registry+https://github.com/rust-lang/crates.io-index" 2075 | checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" 2076 | dependencies = [ 2077 | "time-core", 2078 | ] 2079 | 2080 | [[package]] 2081 | name = "tinyvec" 2082 | version = "1.6.0" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 2085 | dependencies = [ 2086 | "tinyvec_macros", 2087 | ] 2088 | 2089 | [[package]] 2090 | name = "tinyvec_macros" 2091 | version = "0.1.1" 2092 | source = "registry+https://github.com/rust-lang/crates.io-index" 2093 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2094 | 2095 | [[package]] 2096 | name = "tokio" 2097 | version = "1.38.0" 2098 | source = "registry+https://github.com/rust-lang/crates.io-index" 2099 | checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" 2100 | dependencies = [ 2101 | "backtrace", 2102 | "bytes", 2103 | "libc", 2104 | "mio", 2105 | "num_cpus", 2106 | "pin-project-lite", 2107 | "signal-hook-registry", 2108 | "socket2 0.5.5", 2109 | "tokio-macros", 2110 | "windows-sys", 2111 | ] 2112 | 2113 | [[package]] 2114 | name = "tokio-macros" 2115 | version = "2.3.0" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" 2118 | dependencies = [ 2119 | "proc-macro2", 2120 | "quote", 2121 | "syn 2.0.48", 2122 | ] 2123 | 2124 | [[package]] 2125 | name = "tokio-native-tls" 2126 | version = "0.3.1" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2129 | dependencies = [ 2130 | "native-tls", 2131 | "tokio", 2132 | ] 2133 | 2134 | [[package]] 2135 | name = "tokio-util" 2136 | version = "0.7.10" 2137 | source = "registry+https://github.com/rust-lang/crates.io-index" 2138 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 2139 | dependencies = [ 2140 | "bytes", 2141 | "futures-core", 2142 | "futures-sink", 2143 | "pin-project-lite", 2144 | "tokio", 2145 | "tracing", 2146 | ] 2147 | 2148 | [[package]] 2149 | name = "tower-service" 2150 | version = "0.3.2" 2151 | source = "registry+https://github.com/rust-lang/crates.io-index" 2152 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 2153 | 2154 | [[package]] 2155 | name = "tracing" 2156 | version = "0.1.40" 2157 | source = "registry+https://github.com/rust-lang/crates.io-index" 2158 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 2159 | dependencies = [ 2160 | "log", 2161 | "pin-project-lite", 2162 | "tracing-attributes", 2163 | "tracing-core", 2164 | ] 2165 | 2166 | [[package]] 2167 | name = "tracing-attributes" 2168 | version = "0.1.27" 2169 | source = "registry+https://github.com/rust-lang/crates.io-index" 2170 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 2171 | dependencies = [ 2172 | "proc-macro2", 2173 | "quote", 2174 | "syn 2.0.48", 2175 | ] 2176 | 2177 | [[package]] 2178 | name = "tracing-core" 2179 | version = "0.1.32" 2180 | source = "registry+https://github.com/rust-lang/crates.io-index" 2181 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 2182 | dependencies = [ 2183 | "once_cell", 2184 | "valuable", 2185 | ] 2186 | 2187 | [[package]] 2188 | name = "tracing-futures" 2189 | version = "0.2.5" 2190 | source = "registry+https://github.com/rust-lang/crates.io-index" 2191 | checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" 2192 | dependencies = [ 2193 | "pin-project", 2194 | "tracing", 2195 | ] 2196 | 2197 | [[package]] 2198 | name = "tracing-log" 2199 | version = "0.1.4" 2200 | source = "registry+https://github.com/rust-lang/crates.io-index" 2201 | checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" 2202 | dependencies = [ 2203 | "log", 2204 | "once_cell", 2205 | "tracing-core", 2206 | ] 2207 | 2208 | [[package]] 2209 | name = "tracing-subscriber" 2210 | version = "0.3.17" 2211 | source = "registry+https://github.com/rust-lang/crates.io-index" 2212 | checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" 2213 | dependencies = [ 2214 | "matchers", 2215 | "nu-ansi-term", 2216 | "once_cell", 2217 | "regex", 2218 | "sharded-slab", 2219 | "smallvec", 2220 | "thread_local", 2221 | "tracing", 2222 | "tracing-core", 2223 | "tracing-log", 2224 | ] 2225 | 2226 | [[package]] 2227 | name = "try-lock" 2228 | version = "0.2.4" 2229 | source = "registry+https://github.com/rust-lang/crates.io-index" 2230 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 2231 | 2232 | [[package]] 2233 | name = "tungstenite" 2234 | version = "0.17.3" 2235 | source = "registry+https://github.com/rust-lang/crates.io-index" 2236 | checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" 2237 | dependencies = [ 2238 | "base64 0.13.1", 2239 | "byteorder", 2240 | "bytes", 2241 | "http", 2242 | "httparse", 2243 | "log", 2244 | "native-tls", 2245 | "rand", 2246 | "sha-1", 2247 | "thiserror", 2248 | "url", 2249 | "utf-8", 2250 | ] 2251 | 2252 | [[package]] 2253 | name = "typemap_rev" 2254 | version = "0.1.5" 2255 | source = "registry+https://github.com/rust-lang/crates.io-index" 2256 | checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" 2257 | 2258 | [[package]] 2259 | name = "typenum" 2260 | version = "1.17.0" 2261 | source = "registry+https://github.com/rust-lang/crates.io-index" 2262 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2263 | 2264 | [[package]] 2265 | name = "uname" 2266 | version = "0.1.1" 2267 | source = "registry+https://github.com/rust-lang/crates.io-index" 2268 | checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" 2269 | dependencies = [ 2270 | "libc", 2271 | ] 2272 | 2273 | [[package]] 2274 | name = "unicase" 2275 | version = "2.7.0" 2276 | source = "registry+https://github.com/rust-lang/crates.io-index" 2277 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 2278 | dependencies = [ 2279 | "version_check", 2280 | ] 2281 | 2282 | [[package]] 2283 | name = "unicode-bidi" 2284 | version = "0.3.13" 2285 | source = "registry+https://github.com/rust-lang/crates.io-index" 2286 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 2287 | 2288 | [[package]] 2289 | name = "unicode-ident" 2290 | version = "1.0.12" 2291 | source = "registry+https://github.com/rust-lang/crates.io-index" 2292 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 2293 | 2294 | [[package]] 2295 | name = "unicode-normalization" 2296 | version = "0.1.22" 2297 | source = "registry+https://github.com/rust-lang/crates.io-index" 2298 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 2299 | dependencies = [ 2300 | "tinyvec", 2301 | ] 2302 | 2303 | [[package]] 2304 | name = "universal-hash" 2305 | version = "0.4.1" 2306 | source = "registry+https://github.com/rust-lang/crates.io-index" 2307 | checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" 2308 | dependencies = [ 2309 | "generic-array", 2310 | "subtle", 2311 | ] 2312 | 2313 | [[package]] 2314 | name = "unsafe-libyaml" 2315 | version = "0.2.9" 2316 | source = "registry+https://github.com/rust-lang/crates.io-index" 2317 | checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" 2318 | 2319 | [[package]] 2320 | name = "ureq" 2321 | version = "2.8.0" 2322 | source = "registry+https://github.com/rust-lang/crates.io-index" 2323 | checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" 2324 | dependencies = [ 2325 | "base64 0.21.5", 2326 | "log", 2327 | "native-tls", 2328 | "once_cell", 2329 | "url", 2330 | ] 2331 | 2332 | [[package]] 2333 | name = "url" 2334 | version = "2.4.1" 2335 | source = "registry+https://github.com/rust-lang/crates.io-index" 2336 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 2337 | dependencies = [ 2338 | "form_urlencoded", 2339 | "idna", 2340 | "percent-encoding", 2341 | "serde", 2342 | ] 2343 | 2344 | [[package]] 2345 | name = "utf-8" 2346 | version = "0.7.6" 2347 | source = "registry+https://github.com/rust-lang/crates.io-index" 2348 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2349 | 2350 | [[package]] 2351 | name = "uuid" 2352 | version = "0.8.2" 2353 | source = "registry+https://github.com/rust-lang/crates.io-index" 2354 | checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" 2355 | dependencies = [ 2356 | "getrandom", 2357 | ] 2358 | 2359 | [[package]] 2360 | name = "uuid" 2361 | version = "1.5.0" 2362 | source = "registry+https://github.com/rust-lang/crates.io-index" 2363 | checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" 2364 | dependencies = [ 2365 | "serde", 2366 | ] 2367 | 2368 | [[package]] 2369 | name = "valuable" 2370 | version = "0.1.0" 2371 | source = "registry+https://github.com/rust-lang/crates.io-index" 2372 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 2373 | 2374 | [[package]] 2375 | name = "vcpkg" 2376 | version = "0.2.15" 2377 | source = "registry+https://github.com/rust-lang/crates.io-index" 2378 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2379 | 2380 | [[package]] 2381 | name = "version_check" 2382 | version = "0.9.4" 2383 | source = "registry+https://github.com/rust-lang/crates.io-index" 2384 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2385 | 2386 | [[package]] 2387 | name = "want" 2388 | version = "0.3.1" 2389 | source = "registry+https://github.com/rust-lang/crates.io-index" 2390 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2391 | dependencies = [ 2392 | "try-lock", 2393 | ] 2394 | 2395 | [[package]] 2396 | name = "wasi" 2397 | version = "0.11.0+wasi-snapshot-preview1" 2398 | source = "registry+https://github.com/rust-lang/crates.io-index" 2399 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2400 | 2401 | [[package]] 2402 | name = "wasm-bindgen" 2403 | version = "0.2.88" 2404 | source = "registry+https://github.com/rust-lang/crates.io-index" 2405 | checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" 2406 | dependencies = [ 2407 | "cfg-if", 2408 | "wasm-bindgen-macro", 2409 | ] 2410 | 2411 | [[package]] 2412 | name = "wasm-bindgen-backend" 2413 | version = "0.2.88" 2414 | source = "registry+https://github.com/rust-lang/crates.io-index" 2415 | checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" 2416 | dependencies = [ 2417 | "bumpalo", 2418 | "log", 2419 | "once_cell", 2420 | "proc-macro2", 2421 | "quote", 2422 | "syn 2.0.48", 2423 | "wasm-bindgen-shared", 2424 | ] 2425 | 2426 | [[package]] 2427 | name = "wasm-bindgen-futures" 2428 | version = "0.4.38" 2429 | source = "registry+https://github.com/rust-lang/crates.io-index" 2430 | checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" 2431 | dependencies = [ 2432 | "cfg-if", 2433 | "js-sys", 2434 | "wasm-bindgen", 2435 | "web-sys", 2436 | ] 2437 | 2438 | [[package]] 2439 | name = "wasm-bindgen-macro" 2440 | version = "0.2.88" 2441 | source = "registry+https://github.com/rust-lang/crates.io-index" 2442 | checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" 2443 | dependencies = [ 2444 | "quote", 2445 | "wasm-bindgen-macro-support", 2446 | ] 2447 | 2448 | [[package]] 2449 | name = "wasm-bindgen-macro-support" 2450 | version = "0.2.88" 2451 | source = "registry+https://github.com/rust-lang/crates.io-index" 2452 | checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" 2453 | dependencies = [ 2454 | "proc-macro2", 2455 | "quote", 2456 | "syn 2.0.48", 2457 | "wasm-bindgen-backend", 2458 | "wasm-bindgen-shared", 2459 | ] 2460 | 2461 | [[package]] 2462 | name = "wasm-bindgen-shared" 2463 | version = "0.2.88" 2464 | source = "registry+https://github.com/rust-lang/crates.io-index" 2465 | checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" 2466 | 2467 | [[package]] 2468 | name = "wasm-streams" 2469 | version = "0.3.0" 2470 | source = "registry+https://github.com/rust-lang/crates.io-index" 2471 | checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" 2472 | dependencies = [ 2473 | "futures-util", 2474 | "js-sys", 2475 | "wasm-bindgen", 2476 | "wasm-bindgen-futures", 2477 | "web-sys", 2478 | ] 2479 | 2480 | [[package]] 2481 | name = "web-sys" 2482 | version = "0.3.65" 2483 | source = "registry+https://github.com/rust-lang/crates.io-index" 2484 | checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" 2485 | dependencies = [ 2486 | "js-sys", 2487 | "wasm-bindgen", 2488 | ] 2489 | 2490 | [[package]] 2491 | name = "winapi" 2492 | version = "0.3.9" 2493 | source = "registry+https://github.com/rust-lang/crates.io-index" 2494 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2495 | dependencies = [ 2496 | "winapi-i686-pc-windows-gnu", 2497 | "winapi-x86_64-pc-windows-gnu", 2498 | ] 2499 | 2500 | [[package]] 2501 | name = "winapi-i686-pc-windows-gnu" 2502 | version = "0.4.0" 2503 | source = "registry+https://github.com/rust-lang/crates.io-index" 2504 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2505 | 2506 | [[package]] 2507 | name = "winapi-x86_64-pc-windows-gnu" 2508 | version = "0.4.0" 2509 | source = "registry+https://github.com/rust-lang/crates.io-index" 2510 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2511 | 2512 | [[package]] 2513 | name = "windows" 2514 | version = "0.48.0" 2515 | source = "registry+https://github.com/rust-lang/crates.io-index" 2516 | checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" 2517 | dependencies = [ 2518 | "windows-targets", 2519 | ] 2520 | 2521 | [[package]] 2522 | name = "windows-core" 2523 | version = "0.51.1" 2524 | source = "registry+https://github.com/rust-lang/crates.io-index" 2525 | checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" 2526 | dependencies = [ 2527 | "windows-targets", 2528 | ] 2529 | 2530 | [[package]] 2531 | name = "windows-sys" 2532 | version = "0.48.0" 2533 | source = "registry+https://github.com/rust-lang/crates.io-index" 2534 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2535 | dependencies = [ 2536 | "windows-targets", 2537 | ] 2538 | 2539 | [[package]] 2540 | name = "windows-targets" 2541 | version = "0.48.5" 2542 | source = "registry+https://github.com/rust-lang/crates.io-index" 2543 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2544 | dependencies = [ 2545 | "windows_aarch64_gnullvm", 2546 | "windows_aarch64_msvc", 2547 | "windows_i686_gnu", 2548 | "windows_i686_msvc", 2549 | "windows_x86_64_gnu", 2550 | "windows_x86_64_gnullvm", 2551 | "windows_x86_64_msvc", 2552 | ] 2553 | 2554 | [[package]] 2555 | name = "windows_aarch64_gnullvm" 2556 | version = "0.48.5" 2557 | source = "registry+https://github.com/rust-lang/crates.io-index" 2558 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2559 | 2560 | [[package]] 2561 | name = "windows_aarch64_msvc" 2562 | version = "0.48.5" 2563 | source = "registry+https://github.com/rust-lang/crates.io-index" 2564 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2565 | 2566 | [[package]] 2567 | name = "windows_i686_gnu" 2568 | version = "0.48.5" 2569 | source = "registry+https://github.com/rust-lang/crates.io-index" 2570 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2571 | 2572 | [[package]] 2573 | name = "windows_i686_msvc" 2574 | version = "0.48.5" 2575 | source = "registry+https://github.com/rust-lang/crates.io-index" 2576 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2577 | 2578 | [[package]] 2579 | name = "windows_x86_64_gnu" 2580 | version = "0.48.5" 2581 | source = "registry+https://github.com/rust-lang/crates.io-index" 2582 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2583 | 2584 | [[package]] 2585 | name = "windows_x86_64_gnullvm" 2586 | version = "0.48.5" 2587 | source = "registry+https://github.com/rust-lang/crates.io-index" 2588 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2589 | 2590 | [[package]] 2591 | name = "windows_x86_64_msvc" 2592 | version = "0.48.5" 2593 | source = "registry+https://github.com/rust-lang/crates.io-index" 2594 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2595 | 2596 | [[package]] 2597 | name = "winreg" 2598 | version = "0.50.0" 2599 | source = "registry+https://github.com/rust-lang/crates.io-index" 2600 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2601 | dependencies = [ 2602 | "cfg-if", 2603 | "windows-sys", 2604 | ] 2605 | 2606 | [[package]] 2607 | name = "xsalsa20poly1305" 2608 | version = "0.8.0" 2609 | source = "registry+https://github.com/rust-lang/crates.io-index" 2610 | checksum = "e68bcb965d6c650091450b95cea12f07dcd299a01c15e2f9433b0813ea3c0886" 2611 | dependencies = [ 2612 | "aead", 2613 | "poly1305", 2614 | "rand_core", 2615 | "salsa20", 2616 | "subtle", 2617 | "zeroize", 2618 | ] 2619 | 2620 | [[package]] 2621 | name = "zeroize" 2622 | version = "1.3.0" 2623 | source = "registry+https://github.com/rust-lang/crates.io-index" 2624 | checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" 2625 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.73.0-bullseye as builder 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y libopus-dev && \ 5 | rm -rf /var/lib/apt/lists/* 6 | 7 | WORKDIR /root/koe 8 | COPY . . 9 | 10 | RUN --mount=type=cache,target=/root/.cargo/bin \ 11 | --mount=type=cache,target=/root/.cargo/registry/index \ 12 | --mount=type=cache,target=/root/.cargo/registry/cache \ 13 | --mount=type=cache,target=/root/.cargo/git/db \ 14 | --mount=type=cache,target=/root/koe/target \ 15 | cargo build --release --bin koe && \ 16 | cp target/release/koe /usr/local/bin/koe 17 | 18 | ### 19 | 20 | FROM debian:bullseye-slim 21 | 22 | RUN apt-get update && \ 23 | apt-get install -y ca-certificates ffmpeg && \ 24 | rm -rf /var/lib/apt/lists/* 25 | 26 | # Switch to unpriviledged user 27 | RUN useradd --user-group koe 28 | USER koe 29 | 30 | COPY --from=builder /usr/local/bin/koe /usr/local/bin/koe 31 | 32 | ARG SENTRY_RELEASE 33 | ENV SENTRY_RELEASE=$SENTRY_RELEASE 34 | 35 | ENTRYPOINT ["koe"] 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ciffelia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Koe 3 |

4 | 5 |

6 | 7 | CI status 8 | 9 | 10 | MIT License 11 | 12 |

13 | 14 | Koe は、指定されたテキストチャンネルに送信されたメッセージをボイスチャンネルで読み上げる Discord Bot です。 15 | マイクをミュートにしている聞き専メンバーも会話に参加しやすくなります。 16 | 17 | ## 特徴 18 | 19 | - [VOICEVOX ENGINE](https://github.com/VOICEVOX/voicevox_engine) を使った流暢な発音 20 | - 日本語テキストチャットの読み上げに特化 21 | - 特定の語句の読み方を設定する辞書機能を搭載 22 | - Slash Commands に対応 23 | 24 | ## 使い方(コマンド一覧) 25 | 26 | [使い方](docs/user_guide.md)をご覧ください。 27 | 28 | ## インストール 29 | 30 | [セットアップガイド](docs/setup_guide.md)をご覧ください。 31 | 32 | ## 不具合の報告 33 | 34 | [Issues](https://github.com/ciffelia/koe/issues) から日本語または英語で報告をお願いします。 35 | 36 | ## 質問・相談 37 | 38 | [Discussions](https://github.com/ciffelia/koe/discussions) から日本語または英語でご相談ください。 39 | -------------------------------------------------------------------------------- /crates/koe-audio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "koe-audio" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | anyhow = { version = "1.0.82", features = ["backtrace"] } 9 | tokio = { version = "1.37.0", features = ["rt", "process", "io-util"] } 10 | log = "0.4.20" 11 | -------------------------------------------------------------------------------- /crates/koe-audio/src/audio.rs: -------------------------------------------------------------------------------- 1 | use crate::ffmpeg::convert_to_pcm_s16le; 2 | use anyhow::Result; 3 | 4 | /// Representation of encoded (compressed) audio. 5 | pub struct EncodedAudio(Vec); 6 | 7 | impl EncodedAudio { 8 | /// Decode into [`DecodedAudio`] with ffmpeg. 9 | pub async fn decode(self) -> Result { 10 | let decoded_buf = convert_to_pcm_s16le(self.0).await?; 11 | Ok(DecodedAudio::from(decoded_buf)) 12 | } 13 | } 14 | 15 | impl From> for EncodedAudio { 16 | fn from(buf: Vec) -> Self { 17 | Self(buf) 18 | } 19 | } 20 | 21 | impl From for Vec { 22 | fn from(audio: EncodedAudio) -> Self { 23 | audio.0 24 | } 25 | } 26 | 27 | /// Representation of wav audio (16-bit signed little-endian samples). 28 | pub struct DecodedAudio(Vec); 29 | 30 | impl From> for DecodedAudio { 31 | fn from(buf: Vec) -> Self { 32 | Self(buf) 33 | } 34 | } 35 | 36 | impl From for Vec { 37 | fn from(audio: DecodedAudio) -> Self { 38 | audio.0 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /crates/koe-audio/src/ffmpeg.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use log::trace; 3 | use std::process::Stdio; 4 | use tokio::io::AsyncWriteExt; 5 | use tokio::process::Command; 6 | 7 | /// Convert any type of audio source into 16-bit signed little-endian samples (i.e. wav) with ffmpeg. 8 | pub async fn convert_to_pcm_s16le(source: Vec) -> Result> { 9 | let mut child = Command::new("ffmpeg") 10 | // input: stdin 11 | .args(["-i", "pipe:"]) 12 | // format: 16-bit signed little-endian 13 | .args(["-f", "s16le"]) 14 | // channels: 1 (mono) 15 | .args(["-ac", "1"]) 16 | // sampling rate: 48kHz 17 | .args(["-ar", "48000"]) 18 | // codec: pcm 19 | .args(["-acodec", "pcm_s16le"]) 20 | // output: stdout 21 | .arg("-") 22 | .stdin(Stdio::piped()) 23 | .stdout(Stdio::piped()) 24 | .stderr(Stdio::piped()) 25 | .spawn() 26 | .context("Failed to spawn ffmpeg")?; 27 | trace!("Spawned ffmpeg"); 28 | 29 | // Write to stdin in another thread to avoid deadlock: https://doc.rust-lang.org/std/process/struct.Stdio.html#method.piped 30 | let mut stdin = child 31 | .stdin 32 | .take() 33 | .context("Failed to open ffmpeg's stdin")?; 34 | tokio::spawn(async move { 35 | stdin 36 | .write_all(&source) 37 | .await 38 | .expect("Failed to write to ffmpeg's stdin"); 39 | trace!("Wrote to ffmpeg's stdin"); 40 | }); 41 | 42 | let out = child 43 | .wait_with_output() 44 | .await 45 | .context("Failed to read ffmpeg's output")?; 46 | trace!("Received ffmpeg's output"); 47 | 48 | if !out.status.success() { 49 | bail!( 50 | "ffmpeg exited with code {}:\n{}", 51 | out.status, 52 | std::str::from_utf8(&out.stderr)? 53 | ); 54 | } 55 | 56 | Ok(out.stdout) 57 | } 58 | -------------------------------------------------------------------------------- /crates/koe-audio/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod audio; 2 | mod ffmpeg; 3 | 4 | pub use audio::{DecodedAudio, EncodedAudio}; 5 | -------------------------------------------------------------------------------- /crates/koe-call/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "koe-call" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = { version = "1.0.82", features = ["backtrace"] } 8 | tokio = { version = "1.37.0", features = ["sync"] } 9 | serenity = { version = "0.11.7", default-features = false, features = ["native_tls_backend"] } 10 | songbird = { version = "0.3.2", default-features = false, features = ["serenity-native", "driver", "builtin-queue"] } 11 | -------------------------------------------------------------------------------- /crates/koe-call/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context as _, Result}; 2 | use serenity::client::Context; 3 | use songbird::{ 4 | id::{ChannelId, GuildId}, 5 | input::{Codec, Container, Input, Reader}, 6 | join::Join, 7 | Call, Songbird, 8 | }; 9 | use std::sync::Arc; 10 | use tokio::sync::Mutex; 11 | 12 | pub async fn join_deaf( 13 | ctx: &Context, 14 | guild_id: impl Into, 15 | channel_id: impl Into, 16 | ) -> Result<()> { 17 | let manager = extract_songbird(ctx).await?; 18 | let guild_id = guild_id.into(); 19 | let channel_id = channel_id.into(); 20 | 21 | let call = manager.get_or_insert(guild_id); 22 | 23 | // Call::joinを実行するには、2段階のawaitが必要 24 | // 詳細は https://docs.rs/songbird/latest/songbird/struct.Call.html#method.join 25 | let join_res: Result = { 26 | let mut handler = call.lock().await; 27 | handler.deafen(true).await?; 28 | 29 | let join = handler.join(channel_id).await?; 30 | 31 | Ok(join) 32 | }; 33 | let join = join_res?; 34 | join.await?; 35 | 36 | Ok(()) 37 | } 38 | 39 | pub async fn leave(ctx: &Context, guild_id: impl Into) -> Result<()> { 40 | let manager = extract_songbird(ctx).await?; 41 | let guild_id = guild_id.into(); 42 | 43 | manager.remove(guild_id).await?; 44 | 45 | Ok(()) 46 | } 47 | 48 | pub async fn is_connected(ctx: &Context, guild_id: impl Into) -> Result { 49 | let manager = extract_songbird(ctx).await?; 50 | let guild_id = guild_id.into(); 51 | 52 | let is_connected = manager.get(guild_id).is_some(); 53 | 54 | Ok(is_connected) 55 | } 56 | 57 | pub async fn enqueue( 58 | ctx: &Context, 59 | guild_id: impl Into, 60 | raw_audio: Vec, 61 | ) -> Result<()> { 62 | let manager = extract_songbird(ctx).await?; 63 | let call = get_call(manager, guild_id).await?; 64 | 65 | let mut handler = call.lock().await; 66 | handler.enqueue_source(Input::new( 67 | false, 68 | Reader::from_memory(raw_audio), 69 | Codec::Pcm, 70 | Container::Raw, 71 | None, 72 | )); 73 | 74 | Ok(()) 75 | } 76 | 77 | pub async fn skip(ctx: &Context, guild_id: impl Into) -> Result<()> { 78 | let manager = extract_songbird(ctx).await?; 79 | let call = get_call(manager, guild_id).await?; 80 | 81 | let handler = call.lock().await; 82 | let current_track = handler.queue().current(); 83 | 84 | if let Some(track) = current_track { 85 | track.stop().context("Failed to stop current track")?; 86 | } 87 | 88 | Ok(()) 89 | } 90 | 91 | async fn extract_songbird(ctx: &Context) -> Result> { 92 | let songbird = songbird::get(ctx) 93 | .await 94 | .ok_or_else(|| anyhow!("Songbird voice client is not initialized"))?; 95 | 96 | Ok(songbird) 97 | } 98 | 99 | async fn get_call( 100 | manager: Arc, 101 | guild_id: impl Into, 102 | ) -> Result>> { 103 | let guild_id = guild_id.into(); 104 | 105 | let call = manager 106 | .get(guild_id) 107 | .ok_or_else(|| anyhow!("Failed to retrieve call for guild {}", guild_id))?; 108 | 109 | Ok(call) 110 | } 111 | -------------------------------------------------------------------------------- /crates/koe-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "koe-config" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | anyhow = { version = "1.0.82", features = ["backtrace"] } 9 | tokio = { version = "1.37.0", features = ["fs"] } 10 | serde = { version = "1.0.200", features = ["derive"] } 11 | serde_yaml = "0.9.27" 12 | -------------------------------------------------------------------------------- /crates/koe-config/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use serde::Deserialize; 3 | 4 | #[derive(Debug, Clone, Deserialize)] 5 | pub struct Config { 6 | pub discord: DiscordConfig, 7 | pub voicevox: VoicevoxConfig, 8 | pub redis: RedisConfig, 9 | } 10 | 11 | #[derive(Debug, Clone, Deserialize)] 12 | pub struct DiscordConfig { 13 | pub client_id: u64, 14 | pub bot_token: String, 15 | } 16 | 17 | #[derive(Debug, Clone, Deserialize)] 18 | pub struct VoicevoxConfig { 19 | pub api_base: String, 20 | } 21 | 22 | #[derive(Debug, Clone, Deserialize)] 23 | pub struct RedisConfig { 24 | pub url: String, 25 | } 26 | 27 | pub async fn load() -> Result { 28 | let config_path = std::env::var("KOE_CONFIG").unwrap_or_else(|_| "/etc/koe.yaml".to_string()); 29 | 30 | let yaml = tokio::fs::read_to_string(&config_path) 31 | .await 32 | .with_context(|| format!("Failed to load config file from {}", config_path))?; 33 | 34 | let config = serde_yaml::from_str::(&yaml).context("Failed to parse config file")?; 35 | 36 | Ok(config) 37 | } 38 | -------------------------------------------------------------------------------- /crates/koe-db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "koe-db" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = { version = "1.0.82", features = ["backtrace"] } 8 | redis = { version = "0.23.3", default-features = false, features = ["aio", "tokio-comp"] } 9 | -------------------------------------------------------------------------------- /crates/koe-db/src/dict.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use redis::aio::Connection; 3 | use redis::AsyncCommands; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct InsertOption { 7 | pub guild_id: u64, 8 | pub word: String, 9 | pub read_as: String, 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub enum InsertResponse { 14 | Success, 15 | WordAlreadyExists, 16 | } 17 | 18 | /// 辞書に語句を追加する 19 | pub async fn insert(connection: &mut Connection, option: InsertOption) -> Result { 20 | let resp = connection 21 | .hset_nx(dict_key(option.guild_id), option.word, option.read_as) 22 | .await?; 23 | 24 | Ok(match resp { 25 | 0 => InsertResponse::WordAlreadyExists, 26 | 1 => InsertResponse::Success, 27 | x => bail!("Unknown HSETNX response from Redis: {}", x), 28 | }) 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | pub struct RemoveOption { 33 | pub guild_id: u64, 34 | pub word: String, 35 | } 36 | 37 | #[derive(Debug, Clone)] 38 | pub enum RemoveResponse { 39 | Success, 40 | WordDoesNotExist, 41 | } 42 | 43 | /// 辞書から語句を削除する 44 | pub async fn remove(connection: &mut Connection, option: RemoveOption) -> Result { 45 | let resp = connection 46 | .hdel(dict_key(option.guild_id), option.word) 47 | .await?; 48 | 49 | Ok(match resp { 50 | 0 => RemoveResponse::WordDoesNotExist, 51 | 1 => RemoveResponse::Success, 52 | x => bail!("Unknown HDEL response from Redis: {}", x), 53 | }) 54 | } 55 | 56 | #[derive(Debug, Clone)] 57 | pub struct GetAllOption { 58 | pub guild_id: u64, 59 | } 60 | 61 | /// 辞書全体を返す 62 | /// 辞書が存在しないときは空の[`Vec`]を返す 63 | pub async fn get_all( 64 | connection: &mut Connection, 65 | option: GetAllOption, 66 | ) -> Result> { 67 | let resp = connection.hgetall(dict_key(option.guild_id)).await?; 68 | Ok(resp) 69 | } 70 | 71 | fn dict_key(guild_id: u64) -> String { 72 | format!("guild:{}:dict", guild_id) 73 | } 74 | -------------------------------------------------------------------------------- /crates/koe-db/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod dict; 2 | pub mod voice; 3 | 4 | pub use redis; 5 | -------------------------------------------------------------------------------- /crates/koe-db/src/voice.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use redis::aio::Connection; 3 | use redis::AsyncCommands; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct GetOption { 7 | pub guild_id: u64, 8 | pub user_id: u64, 9 | pub fallback: i64, 10 | } 11 | 12 | /// ユーザーの声を返す 13 | /// 未設定の場合は`option.fallback`の値を設定して返す 14 | pub async fn get(connection: &mut Connection, option: GetOption) -> Result { 15 | let key = voice_key(option.guild_id, option.user_id); 16 | 17 | let (resp,) = redis::pipe() 18 | .set_nx(&key, option.fallback) 19 | .ignore() 20 | .get(&key) 21 | .query_async(connection) 22 | .await?; 23 | 24 | Ok(resp) 25 | } 26 | 27 | #[derive(Debug, Clone)] 28 | pub struct SetOption { 29 | pub guild_id: u64, 30 | pub user_id: u64, 31 | pub value: i64, 32 | } 33 | 34 | /// ユーザーの声を設定する 35 | pub async fn set(connection: &mut Connection, option: SetOption) -> Result<()> { 36 | let key = voice_key(option.guild_id, option.user_id); 37 | connection.set(&key, option.value).await?; 38 | Ok(()) 39 | } 40 | 41 | fn voice_key(guild_id: u64, user_id: u64) -> String { 42 | format!("guild:{}:user:{}:voice", guild_id, user_id) 43 | } 44 | -------------------------------------------------------------------------------- /crates/koe-speech/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "koe-speech" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | koe-audio = { path = "../koe-audio" } 9 | anyhow = { version = "1.0.82", features = ["backtrace"] } 10 | serde = { version = "1.0.200", features = ["derive"] } 11 | reqwest = { version = "0.11.22", features = ["json"] } 12 | -------------------------------------------------------------------------------- /crates/koe-speech/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod speech; 2 | pub mod voicevox; 3 | -------------------------------------------------------------------------------- /crates/koe-speech/src/speech.rs: -------------------------------------------------------------------------------- 1 | use crate::voicevox::{GenerateQueryFromPresetParams, Preset, SynthesisParams, VoicevoxClient}; 2 | use anyhow::{anyhow, Result}; 3 | use koe_audio::EncodedAudio; 4 | 5 | pub async fn initialize_speakers(client: &VoicevoxClient) -> Result<()> { 6 | let preset_list = client.presets().await?; 7 | for preset in preset_list { 8 | client.initialize_speaker(preset.style_id).await?; 9 | } 10 | Ok(()) 11 | } 12 | 13 | pub async fn make_speech(client: &VoicevoxClient, option: SpeechRequest) -> Result { 14 | let preset = get_preset(client, option.preset_id).await?; 15 | 16 | let query = client 17 | .generate_query_from_preset(GenerateQueryFromPresetParams { 18 | preset_id: preset.id, 19 | text: option.text, 20 | }) 21 | .await?; 22 | 23 | let audio = client 24 | .synthesis(SynthesisParams { 25 | style_id: preset.style_id, 26 | query, 27 | }) 28 | .await?; 29 | 30 | Ok(audio) 31 | } 32 | 33 | pub async fn list_preset_ids(client: &VoicevoxClient) -> Result> { 34 | let preset_list = client.presets().await?; 35 | let ids = preset_list.into_iter().map(|p| PresetId(p.id)).collect(); 36 | Ok(ids) 37 | } 38 | 39 | async fn get_preset(client: &VoicevoxClient, id: PresetId) -> Result { 40 | let preset_list = client.presets().await?; 41 | 42 | let preset = preset_list 43 | .into_iter() 44 | .find(|p| PresetId(p.id) == id) 45 | .ok_or_else(|| anyhow!("Preset {} is not available", id.0))?; 46 | 47 | Ok(preset) 48 | } 49 | 50 | #[derive(Debug, Clone)] 51 | pub struct SpeechRequest { 52 | pub text: String, 53 | pub preset_id: PresetId, 54 | } 55 | 56 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 57 | pub struct PresetId(pub i64); 58 | 59 | impl From for PresetId { 60 | fn from(x: i64) -> Self { 61 | Self(x) 62 | } 63 | } 64 | 65 | impl From<&i64> for PresetId { 66 | fn from(x: &i64) -> Self { 67 | Self(*x) 68 | } 69 | } 70 | 71 | impl From for i64 { 72 | fn from(x: PresetId) -> Self { 73 | x.0 74 | } 75 | } 76 | 77 | impl From<&PresetId> for i64 { 78 | fn from(x: &PresetId) -> Self { 79 | x.0 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/koe-speech/src/voicevox.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use koe_audio::EncodedAudio; 3 | use reqwest::Url; 4 | use serde::Deserialize; 5 | 6 | pub struct VoicevoxClient { 7 | client: reqwest::Client, 8 | api_base: String, 9 | } 10 | 11 | impl VoicevoxClient { 12 | pub fn new(api_base: String) -> Self { 13 | Self { 14 | client: reqwest::Client::new(), 15 | api_base, 16 | } 17 | } 18 | 19 | pub async fn generate_query_from_preset( 20 | &self, 21 | params: GenerateQueryFromPresetParams, 22 | ) -> Result { 23 | let url = Url::parse_with_params( 24 | &self.get_endpoint("/audio_query_from_preset"), 25 | &[ 26 | ("text", params.text), 27 | ("preset_id", params.preset_id.to_string()), 28 | ], 29 | )?; 30 | 31 | let resp = self 32 | .client 33 | .post(url) 34 | .send() 35 | .await? 36 | .error_for_status()? 37 | .text() 38 | .await?; 39 | 40 | Ok(resp) 41 | } 42 | 43 | pub async fn synthesis(&self, params: SynthesisParams) -> Result { 44 | let url = Url::parse_with_params( 45 | &self.get_endpoint("/synthesis"), 46 | &[("speaker", params.style_id.to_string())], 47 | )?; 48 | 49 | let resp = self 50 | .client 51 | .post(url) 52 | .header("content-type", "application/json") 53 | .body(params.query) 54 | .send() 55 | .await? 56 | .error_for_status()? 57 | .bytes() 58 | .await?; 59 | 60 | Ok(EncodedAudio::from(resp.to_vec())) 61 | } 62 | 63 | pub async fn presets(&self) -> Result> { 64 | let url = Url::parse(&self.get_endpoint("/presets"))?; 65 | 66 | let resp = self 67 | .client 68 | .get(url) 69 | .send() 70 | .await? 71 | .error_for_status()? 72 | .json() 73 | .await?; 74 | 75 | Ok(resp) 76 | } 77 | 78 | pub async fn initialize_speaker(&self, speaker_id: i64) -> Result<()> { 79 | let url = Url::parse_with_params( 80 | &self.get_endpoint("/initialize_speaker"), 81 | &[("speaker", speaker_id.to_string())], 82 | )?; 83 | 84 | self.client.post(url).send().await?.error_for_status()?; 85 | 86 | Ok(()) 87 | } 88 | 89 | fn get_endpoint(&self, path: impl AsRef) -> String { 90 | self.api_base.clone() + path.as_ref() 91 | } 92 | } 93 | 94 | #[derive(Debug, Clone)] 95 | pub struct GenerateQueryFromPresetParams { 96 | pub preset_id: i64, 97 | pub text: String, 98 | } 99 | 100 | #[derive(Debug, Clone)] 101 | pub struct SynthesisParams { 102 | pub style_id: i64, 103 | pub query: String, 104 | } 105 | 106 | #[derive(Debug, Clone, Deserialize)] 107 | pub struct Preset { 108 | pub id: i64, 109 | pub name: String, 110 | pub speaker_uuid: String, 111 | pub style_id: i64, 112 | #[serde(rename = "speedScale")] 113 | pub speed_scale: f64, 114 | #[serde(rename = "pitchScale")] 115 | pub pitch_scale: f64, 116 | #[serde(rename = "intonationScale")] 117 | pub intonation_scale: f64, 118 | #[serde(rename = "volumeScale")] 119 | pub volume_scale: f64, 120 | #[serde(rename = "prePhonemeLength")] 121 | pub pre_phoneme_length: f64, 122 | #[serde(rename = "postPhonemeLength")] 123 | pub post_phoneme_length: f64, 124 | } 125 | -------------------------------------------------------------------------------- /crates/koe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "koe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | koe-audio = { path = "../koe-audio" } 9 | koe-call = { path = "../koe-call" } 10 | koe-config = { path = "../koe-config" } 11 | koe-db = { path = "../koe-db" } 12 | koe-speech = { path = "../koe-speech" } 13 | 14 | # Basics 15 | anyhow = { version = "1.0.82", features = ["backtrace"] } 16 | tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros", "sync", "time"] } 17 | 18 | # Logging 19 | log = "0.4.20" 20 | ecs-logger = "1.1.0" 21 | 22 | # Error reporting 23 | sentry = { version = "0.31.8", features = ["anyhow"] } 24 | 25 | # Discord 26 | serenity = { version = "0.11.7", default-features = false, features = ["cache", "client", "utils", "voice", "native_tls_backend"] } 27 | songbird = { version = "0.3.2", default-features = false, features = ["serenity-native", "driver"] } 28 | discord-md = "3.0.0" 29 | 30 | # Utilities 31 | dashmap = "5.5.3" 32 | once_cell = "1.19.0" 33 | aho-corasick = "1.1.3" 34 | regex = "1.10.4" 35 | rand = "0.8.5" 36 | -------------------------------------------------------------------------------- /crates/koe/src/app_state.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use dashmap::DashMap; 3 | use koe_db::redis; 4 | use koe_speech::voicevox::VoicevoxClient; 5 | use serenity::{ 6 | client::{Client, Context}, 7 | model::{ 8 | channel::Message, 9 | id::{ChannelId, GuildId}, 10 | }, 11 | prelude::TypeMapKey, 12 | }; 13 | use std::sync::Arc; 14 | 15 | pub struct AppState { 16 | pub redis_client: redis::Client, 17 | pub voicevox_client: VoicevoxClient, 18 | pub connected_guild_states: DashMap, 19 | } 20 | 21 | pub struct ConnectedGuildState { 22 | pub bound_text_channel: ChannelId, 23 | pub last_message_read: Option, 24 | } 25 | 26 | impl TypeMapKey for AppState { 27 | type Value = Arc; 28 | } 29 | 30 | pub async fn initialize(client: &Client, state: AppState) { 31 | let mut data = client.data.write().await; 32 | data.insert::(Arc::new(state)); 33 | } 34 | 35 | pub async fn get(ctx: &Context) -> Result> { 36 | let data = ctx.data.read().await; 37 | 38 | let state_ref = data 39 | .get::() 40 | .ok_or_else(|| anyhow!("AppState is not initialized"))?; 41 | 42 | Ok(state_ref.clone()) 43 | } 44 | -------------------------------------------------------------------------------- /crates/koe/src/command/handler.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | model::{Command, DictAddOption, DictRemoveOption}, 3 | parser::parse, 4 | }; 5 | use crate::{app_state, component_interaction::custom_id}; 6 | use anyhow::{anyhow, bail, Context as _, Result}; 7 | use koe_db::{ 8 | dict::{GetAllOption, InsertOption, InsertResponse, RemoveOption, RemoveResponse}, 9 | voice::GetOption, 10 | }; 11 | use rand::seq::SliceRandom; 12 | use serenity::{ 13 | builder::{ 14 | CreateActionRow, CreateComponents, CreateEmbed, CreateSelectMenu, CreateSelectMenuOption, 15 | }, 16 | client::Context, 17 | model::{ 18 | application::interaction::{ 19 | application_command::ApplicationCommandInteraction, InteractionResponseType, 20 | MessageFlags, 21 | }, 22 | id::{ChannelId, GuildId, UserId}, 23 | }, 24 | }; 25 | 26 | pub async fn handle(ctx: &Context, cmd: &ApplicationCommandInteraction) -> Result<()> { 27 | match parse(cmd) { 28 | Command::Join => handle_join(ctx, cmd) 29 | .await 30 | .context("Failed to execute /join")?, 31 | Command::Leave => handle_leave(ctx, cmd) 32 | .await 33 | .context("Failed to execute /leave")?, 34 | Command::Skip => handle_skip(ctx, cmd) 35 | .await 36 | .context("Failed to execute /skip")?, 37 | Command::Voice => handle_voice(ctx, cmd) 38 | .await 39 | .context("Failed to execute /voice")?, 40 | Command::DictAdd(option) => handle_dict_add(ctx, cmd, option) 41 | .await 42 | .context("Failed to execute /dict add")?, 43 | Command::DictRemove(option) => handle_dict_remove(ctx, cmd, option) 44 | .await 45 | .context("Failed to execute /dict remove")?, 46 | Command::DictView => handle_dict_view(ctx, cmd) 47 | .await 48 | .context("Failed to execute /dict view")?, 49 | Command::Help => handle_help(ctx, cmd) 50 | .await 51 | .context("Failed to execute /help")?, 52 | Command::Unknown => { 53 | bail!("Unknown command: {:?}", cmd); 54 | } 55 | }; 56 | 57 | Ok(()) 58 | } 59 | 60 | async fn handle_join(ctx: &Context, cmd: &ApplicationCommandInteraction) -> Result<()> { 61 | let guild_id = match cmd.guild_id { 62 | Some(id) => id, 63 | None => { 64 | r(ctx, cmd, "`/join`, `/kjoin` はサーバー内でのみ使えます。").await?; 65 | return Ok(()); 66 | } 67 | }; 68 | let user_id = cmd.user.id; 69 | let text_channel_id = cmd.channel_id; 70 | 71 | let voice_channel_id = match get_user_voice_channel(ctx, &guild_id, &user_id)? { 72 | Some(channel) => channel, 73 | None => { 74 | r( 75 | ctx, 76 | cmd, 77 | "ボイスチャンネルに接続してから `/join` を送信してください。", 78 | ) 79 | .await?; 80 | return Ok(()); 81 | } 82 | }; 83 | 84 | koe_call::join_deaf(ctx, guild_id, voice_channel_id).await?; 85 | 86 | let state = app_state::get(ctx).await?; 87 | state.connected_guild_states.insert( 88 | guild_id, 89 | app_state::ConnectedGuildState { 90 | bound_text_channel: text_channel_id, 91 | last_message_read: None, 92 | }, 93 | ); 94 | 95 | r(ctx, cmd, "接続しました。").await?; 96 | Ok(()) 97 | } 98 | 99 | async fn handle_leave(ctx: &Context, cmd: &ApplicationCommandInteraction) -> Result<()> { 100 | let guild_id = match cmd.guild_id { 101 | Some(id) => id, 102 | None => { 103 | r(ctx, cmd, "`/leave`, `/kleave` はサーバー内でのみ使えます。").await?; 104 | return Ok(()); 105 | } 106 | }; 107 | 108 | if !koe_call::is_connected(ctx, guild_id).await? { 109 | { 110 | r(ctx, cmd, "どのボイスチャンネルにも接続していません。").await?; 111 | return Ok(()); 112 | }; 113 | } 114 | 115 | koe_call::leave(ctx, guild_id).await?; 116 | 117 | let state = app_state::get(ctx).await?; 118 | state.connected_guild_states.remove(&guild_id); 119 | 120 | r(ctx, cmd, "切断しました。").await?; 121 | Ok(()) 122 | } 123 | 124 | async fn handle_skip(ctx: &Context, cmd: &ApplicationCommandInteraction) -> Result<()> { 125 | let guild_id = match cmd.guild_id { 126 | Some(id) => id, 127 | None => { 128 | r(ctx, cmd, "`/skip`, `/kskip` はサーバー内でのみ使えます。").await?; 129 | return Ok(()); 130 | } 131 | }; 132 | 133 | if !koe_call::is_connected(ctx, guild_id).await? { 134 | { 135 | r(ctx, cmd, "どのボイスチャンネルにも接続していません。").await?; 136 | return Ok(()); 137 | }; 138 | } 139 | 140 | koe_call::skip(ctx, guild_id).await?; 141 | 142 | r(ctx, cmd, "読み上げ中のメッセージをスキップしました。").await?; 143 | Ok(()) 144 | } 145 | 146 | async fn handle_voice(ctx: &Context, cmd: &ApplicationCommandInteraction) -> Result<()> { 147 | let guild_id = match cmd.guild_id { 148 | Some(id) => id, 149 | None => { 150 | r(ctx, cmd, "`/voice` はサーバー内でのみ使えます。").await?; 151 | return Ok(()); 152 | } 153 | }; 154 | 155 | let state = app_state::get(ctx).await?; 156 | 157 | let available_presets = state.voicevox_client.presets().await?; 158 | let fallback_preset_id = available_presets 159 | .choose(&mut rand::thread_rng()) 160 | .map(|p| p.id) 161 | .ok_or_else(|| anyhow!("No presets available"))?; 162 | 163 | let mut conn = state.redis_client.get_async_connection().await?; 164 | let current_preset = koe_db::voice::get( 165 | &mut conn, 166 | GetOption { 167 | guild_id: guild_id.into(), 168 | user_id: cmd.user.id.into(), 169 | fallback: fallback_preset_id, 170 | }, 171 | ) 172 | .await?; 173 | 174 | { 175 | let option_list = available_presets 176 | .iter() 177 | .map(|p| { 178 | let mut option = CreateSelectMenuOption::default(); 179 | option 180 | .label(&p.name) 181 | .value(p.id) 182 | .default_selection(p.id == current_preset); 183 | option 184 | }) 185 | .collect::>(); 186 | 187 | let mut select = CreateSelectMenu::default(); 188 | select.custom_id(custom_id::CUSTOM_ID_VOICE); 189 | select.options(|create_options| create_options.set_options(option_list)); 190 | 191 | let mut action_row = CreateActionRow::default(); 192 | action_row.add_select_menu(select); 193 | 194 | let mut components = CreateComponents::default(); 195 | components.add_action_row(action_row); 196 | 197 | cmd.create_interaction_response(&ctx.http, |create_response| { 198 | create_response 199 | .kind(InteractionResponseType::ChannelMessageWithSource) 200 | .interaction_response_data(|create_message| { 201 | create_message 202 | .flags(MessageFlags::EPHEMERAL) 203 | .set_components(components) 204 | }) 205 | }) 206 | .await 207 | .context("Failed to create interaction response")?; 208 | }; 209 | 210 | Ok(()) 211 | } 212 | 213 | async fn handle_dict_add( 214 | ctx: &Context, 215 | cmd: &ApplicationCommandInteraction, 216 | option: DictAddOption, 217 | ) -> Result<()> { 218 | let guild_id = match cmd.guild_id { 219 | Some(id) => id, 220 | None => { 221 | r(ctx, cmd, "`/dict add` はサーバー内でのみ使えます。").await?; 222 | return Ok(()); 223 | } 224 | }; 225 | 226 | let state = app_state::get(ctx).await?; 227 | let mut conn = state.redis_client.get_async_connection().await?; 228 | 229 | let resp = koe_db::dict::insert( 230 | &mut conn, 231 | InsertOption { 232 | guild_id: guild_id.into(), 233 | word: option.word.clone(), 234 | read_as: option.read_as.clone(), 235 | }, 236 | ) 237 | .await?; 238 | 239 | let msg = match resp { 240 | InsertResponse::Success => format!( 241 | "{}の読み方を{}として辞書に登録しました。", 242 | sanitize_response(&option.word), 243 | sanitize_response(&option.read_as) 244 | ), 245 | InsertResponse::WordAlreadyExists => format!( 246 | "すでに{}は辞書に登録されています。", 247 | sanitize_response(&option.word) 248 | ), 249 | }; 250 | r(ctx, cmd, msg).await?; 251 | Ok(()) 252 | } 253 | 254 | async fn handle_dict_remove( 255 | ctx: &Context, 256 | cmd: &ApplicationCommandInteraction, 257 | option: DictRemoveOption, 258 | ) -> Result<()> { 259 | let guild_id = match cmd.guild_id { 260 | Some(id) => id, 261 | None => { 262 | r(ctx, cmd, "`/dict remove` はサーバー内でのみ使えます。").await?; 263 | return Ok(()); 264 | } 265 | }; 266 | 267 | let state = app_state::get(ctx).await?; 268 | let mut conn = state.redis_client.get_async_connection().await?; 269 | 270 | let resp = koe_db::dict::remove( 271 | &mut conn, 272 | RemoveOption { 273 | guild_id: guild_id.into(), 274 | word: option.word.clone(), 275 | }, 276 | ) 277 | .await?; 278 | 279 | let msg = match resp { 280 | RemoveResponse::Success => format!( 281 | "辞書から{}を削除しました。", 282 | sanitize_response(&option.word) 283 | ), 284 | RemoveResponse::WordDoesNotExist => format!( 285 | "{}は辞書に登録されていません。", 286 | sanitize_response(&option.word) 287 | ), 288 | }; 289 | r(ctx, cmd, msg).await?; 290 | Ok(()) 291 | } 292 | 293 | async fn handle_dict_view(ctx: &Context, cmd: &ApplicationCommandInteraction) -> Result<()> { 294 | let guild_id = match cmd.guild_id { 295 | Some(id) => id, 296 | None => { 297 | r(ctx, cmd, "`/dict view` はサーバー内でのみ使えます。").await?; 298 | return Ok(()); 299 | } 300 | }; 301 | 302 | let state = app_state::get(ctx).await?; 303 | let mut conn = state.redis_client.get_async_connection().await?; 304 | 305 | let dict = koe_db::dict::get_all( 306 | &mut conn, 307 | GetAllOption { 308 | guild_id: guild_id.into(), 309 | }, 310 | ) 311 | .await?; 312 | 313 | { 314 | let mut embed = CreateEmbed::default(); 315 | 316 | let guild_name = guild_id 317 | .name(&ctx.cache) 318 | .unwrap_or_else(|| "サーバー".to_string()); 319 | embed.title(format!("📕 {}の辞書", guild_name)); 320 | 321 | embed.fields( 322 | dict.into_iter() 323 | .map(|(word, read_as)| (word, sanitize_response(&read_as), false)), 324 | ); 325 | 326 | cmd.create_interaction_response(&ctx.http, |create_response| { 327 | create_response 328 | .kind(InteractionResponseType::ChannelMessageWithSource) 329 | .interaction_response_data(|create_message| create_message.add_embed(embed)) 330 | }) 331 | .await 332 | .context("Failed to create interaction response")?; 333 | }; 334 | 335 | Ok(()) 336 | } 337 | 338 | async fn handle_help(ctx: &Context, cmd: &ApplicationCommandInteraction) -> Result<()> { 339 | r( 340 | ctx, 341 | cmd, 342 | "使い方はこちらをご覧ください:\nhttps://github.com/ciffelia/koe/blob/main/docs/user_guide.md", 343 | ) 344 | .await?; 345 | Ok(()) 346 | } 347 | 348 | fn get_user_voice_channel( 349 | ctx: &Context, 350 | guild_id: &GuildId, 351 | user_id: &UserId, 352 | ) -> Result> { 353 | let guild = guild_id 354 | .to_guild_cached(&ctx.cache) 355 | .context("Failed to find guild in the cache")?; 356 | 357 | let channel_id = guild 358 | .voice_states 359 | .get(user_id) 360 | .and_then(|voice_state| voice_state.channel_id); 361 | 362 | Ok(channel_id) 363 | } 364 | 365 | // Helper function to create text message response 366 | async fn r(ctx: &Context, cmd: &ApplicationCommandInteraction, text: impl ToString) -> Result<()> { 367 | cmd.create_interaction_response(&ctx.http, |create_response| { 368 | create_response 369 | .kind(InteractionResponseType::ChannelMessageWithSource) 370 | .interaction_response_data(|create_message| create_message.content(text)) 371 | }) 372 | .await 373 | .context("Failed to create interaction response")?; 374 | 375 | Ok(()) 376 | } 377 | 378 | fn sanitize_response(text: &str) -> String { 379 | format!("`{}`", text.replace('`', "")) 380 | } 381 | -------------------------------------------------------------------------------- /crates/koe/src/command/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handler; 2 | mod model; 3 | mod parser; 4 | pub mod setup; 5 | -------------------------------------------------------------------------------- /crates/koe/src/command/model.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub enum Command { 3 | Join, 4 | Leave, 5 | Skip, 6 | Voice, 7 | DictAdd(DictAddOption), 8 | DictRemove(DictRemoveOption), 9 | DictView, 10 | Help, 11 | Unknown, 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct DictAddOption { 16 | pub word: String, 17 | pub read_as: String, 18 | } 19 | 20 | #[derive(Debug, Clone)] 21 | pub struct DictRemoveOption { 22 | pub word: String, 23 | } 24 | -------------------------------------------------------------------------------- /crates/koe/src/command/parser.rs: -------------------------------------------------------------------------------- 1 | use super::model::{Command, DictAddOption, DictRemoveOption}; 2 | use serenity::model::application::interaction::application_command::{ 3 | ApplicationCommandInteraction, CommandDataOptionValue, 4 | }; 5 | 6 | pub fn parse(cmd: &ApplicationCommandInteraction) -> Command { 7 | match cmd.data.name.as_str() { 8 | "join" | "kjoin" => Command::Join, 9 | "leave" | "kleave" => Command::Leave, 10 | "skip" | "kskip" => Command::Skip, 11 | "voice" => Command::Voice, 12 | "dict" => parse_dict(cmd), 13 | "help" => Command::Help, 14 | _ => Command::Unknown, 15 | } 16 | } 17 | 18 | fn parse_dict(cmd: &ApplicationCommandInteraction) -> Command { 19 | let option_dict = match cmd.data.options.get(0) { 20 | Some(option) => option, 21 | None => return Command::Unknown, 22 | }; 23 | 24 | match option_dict.name.as_str() { 25 | "add" => { 26 | let option_word = match option_dict.options.get(0) { 27 | Some(x) => x, 28 | None => return Command::Unknown, 29 | }; 30 | let option_read_as = match option_dict.options.get(1) { 31 | Some(x) => x, 32 | None => return Command::Unknown, 33 | }; 34 | let word = match &option_word.resolved { 35 | Some(CommandDataOptionValue::String(x)) => x, 36 | _ => return Command::Unknown, 37 | }; 38 | let read_as = match &option_read_as.resolved { 39 | Some(CommandDataOptionValue::String(x)) => x, 40 | _ => return Command::Unknown, 41 | }; 42 | 43 | Command::DictAdd(DictAddOption { 44 | word: word.clone(), 45 | read_as: read_as.clone(), 46 | }) 47 | } 48 | "remove" => { 49 | let option_word = match option_dict.options.get(0) { 50 | Some(x) => x, 51 | None => return Command::Unknown, 52 | }; 53 | let word = match &option_word.resolved { 54 | Some(CommandDataOptionValue::String(x)) => x, 55 | _ => return Command::Unknown, 56 | }; 57 | 58 | Command::DictRemove(DictRemoveOption { word: word.clone() }) 59 | } 60 | "view" => Command::DictView, 61 | _ => Command::Unknown, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/koe/src/command/setup.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context as _, Result}; 2 | use serenity::{ 3 | client::Context, 4 | model::{application::command::CommandOptionType, id::GuildId}, 5 | }; 6 | 7 | pub async fn setup_guild_commands(ctx: &Context, guild_id: GuildId) -> Result<()> { 8 | guild_id 9 | .set_application_commands(&ctx.http, |commands| { 10 | commands 11 | .create_application_command(|command| { 12 | command.name("help").description("使い方を表示") 13 | }) 14 | .create_application_command(|command| { 15 | command 16 | .name("join") 17 | .description("ボイスチャンネルに接続し、読み上げを開始") 18 | }) 19 | .create_application_command(|command| { 20 | command 21 | .name("kjoin") 22 | .description("ボイスチャンネルに接続し、読み上げを開始") 23 | }) 24 | .create_application_command(|command| { 25 | command 26 | .name("leave") 27 | .description("ボイスチャンネルから退出") 28 | }) 29 | .create_application_command(|command| { 30 | command 31 | .name("kleave") 32 | .description("ボイスチャンネルから退出") 33 | }) 34 | .create_application_command(|command| { 35 | command 36 | .name("skip") 37 | .description("読み上げ中のメッセージをスキップ") 38 | }) 39 | .create_application_command(|command| { 40 | command 41 | .name("kskip") 42 | .description("読み上げ中のメッセージをスキップ") 43 | }) 44 | .create_application_command(|command| { 45 | command.name("voice").description("話者の設定") 46 | }) 47 | .create_application_command(|command| { 48 | command 49 | .name("dict") 50 | .description("読み上げ辞書の閲覧と編集") 51 | .create_option(|option| { 52 | option 53 | .name("add") 54 | .description("辞書に項目を追加") 55 | .kind(CommandOptionType::SubCommand) 56 | .create_sub_option(|option| { 57 | option 58 | .name("word") 59 | .description("読み方を指定したい語句") 60 | .kind(CommandOptionType::String) 61 | .required(true) 62 | }) 63 | .create_sub_option(|option| { 64 | option 65 | .name("read-as") 66 | .description("語句の読み方") 67 | .kind(CommandOptionType::String) 68 | .required(true) 69 | }) 70 | }) 71 | .create_option(|option| { 72 | option 73 | .name("remove") 74 | .description("辞書から項目を削除") 75 | .kind(CommandOptionType::SubCommand) 76 | .create_sub_option(|option| { 77 | option 78 | .name("word") 79 | .description("削除したい語句") 80 | .kind(CommandOptionType::String) 81 | .required(true) 82 | }) 83 | }) 84 | .create_option(|option| { 85 | option 86 | .name("view") 87 | .description("辞書を表示") 88 | .kind(CommandOptionType::SubCommand) 89 | }) 90 | }) 91 | }) 92 | .await 93 | .context("Failed to set guild application commands")?; 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /crates/koe/src/component_interaction/custom_id.rs: -------------------------------------------------------------------------------- 1 | pub const CUSTOM_ID_VOICE: &str = "voice"; 2 | -------------------------------------------------------------------------------- /crates/koe/src/component_interaction/handler.rs: -------------------------------------------------------------------------------- 1 | use super::custom_id; 2 | use crate::app_state; 3 | use anyhow::{anyhow, bail, Context as _, Result}; 4 | use koe_db::voice::SetOption; 5 | use serenity::{ 6 | client::Context, 7 | model::application::interaction::{ 8 | message_component::MessageComponentInteraction, InteractionResponseType, 9 | }, 10 | }; 11 | 12 | pub async fn handle(ctx: &Context, interaction: &MessageComponentInteraction) -> Result<()> { 13 | if interaction.data.custom_id == custom_id::CUSTOM_ID_VOICE { 14 | handle_voice(ctx, interaction) 15 | .await 16 | .context(r#"Failed to handle "voice" message component interaction"#)?; 17 | } else { 18 | bail!( 19 | "Unknown message component interaction custom_id: {}", 20 | interaction.data.custom_id 21 | ); 22 | } 23 | 24 | Ok(()) 25 | } 26 | 27 | async fn handle_voice(ctx: &Context, interaction: &MessageComponentInteraction) -> Result<()> { 28 | let guild_id = interaction 29 | .guild_id 30 | .ok_or_else(|| anyhow!("Failed to get guild ID"))?; 31 | 32 | let selected_preset_id = interaction 33 | .data 34 | .values 35 | .get(0) 36 | .ok_or_else(|| anyhow!("Value not available in message component interaction"))? 37 | .parse::()?; 38 | 39 | let state = app_state::get(ctx).await?; 40 | 41 | let available_presets = state.voicevox_client.presets().await?; 42 | let selected_preset = available_presets 43 | .into_iter() 44 | .find(|p| p.id == selected_preset_id) 45 | .ok_or_else(|| anyhow!("Preset {} not available", selected_preset_id))?; 46 | 47 | let mut conn = state.redis_client.get_async_connection().await?; 48 | koe_db::voice::set( 49 | &mut conn, 50 | SetOption { 51 | guild_id: guild_id.into(), 52 | user_id: interaction.user.id.into(), 53 | value: selected_preset_id, 54 | }, 55 | ) 56 | .await?; 57 | 58 | r( 59 | ctx, 60 | interaction, 61 | format!( 62 | "<@{}>さんの声を`{}`に変更しました。", 63 | interaction.user.id, selected_preset.name 64 | ), 65 | ) 66 | .await?; 67 | Ok(()) 68 | } 69 | 70 | // Helper function to create text message response 71 | async fn r( 72 | ctx: &Context, 73 | interaction: &MessageComponentInteraction, 74 | text: impl ToString, 75 | ) -> Result<()> { 76 | interaction 77 | .create_interaction_response(&ctx.http, |create_response| { 78 | create_response 79 | .kind(InteractionResponseType::ChannelMessageWithSource) 80 | .interaction_response_data(|create_message| create_message.content(text)) 81 | }) 82 | .await 83 | .context("Failed to create interaction response")?; 84 | 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /crates/koe/src/component_interaction/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod custom_id; 2 | pub mod handler; 3 | -------------------------------------------------------------------------------- /crates/koe/src/error.rs: -------------------------------------------------------------------------------- 1 | use log::error; 2 | use sentry::integrations::anyhow::capture_anyhow; 3 | 4 | pub fn report_error(err: impl Into) { 5 | let err = err.into(); 6 | 7 | error!("{:?}", err); 8 | capture_anyhow(&err); 9 | } 10 | -------------------------------------------------------------------------------- /crates/koe/src/event_handler.rs: -------------------------------------------------------------------------------- 1 | use crate::error::report_error; 2 | use crate::{command, voice_state}; 3 | use crate::{component_interaction, message}; 4 | use anyhow::Context as _; 5 | use log::info; 6 | use serenity::{ 7 | async_trait, 8 | client::{Context, EventHandler}, 9 | model::{ 10 | application::interaction::Interaction, 11 | channel::Message, 12 | gateway::{Activity, Ready}, 13 | guild::Guild, 14 | voice::VoiceState, 15 | }, 16 | }; 17 | 18 | pub struct Handler; 19 | 20 | #[async_trait] 21 | impl EventHandler for Handler { 22 | async fn ready(&self, ctx: Context, ready: Ready) { 23 | info!("Connected as {}", ready.user.name); 24 | 25 | ctx.set_activity(Activity::playing("テキストチャット 読み上げBot")) 26 | .await; 27 | 28 | for guild in &ready.guilds { 29 | if let Err(err) = command::setup::setup_guild_commands(&ctx, guild.id) 30 | .await 31 | .context("Failed to set guild application commands") 32 | { 33 | report_error(err); 34 | } 35 | } 36 | } 37 | 38 | async fn guild_create(&self, ctx: Context, guild: Guild, _is_new: bool) { 39 | if let Err(err) = command::setup::setup_guild_commands(&ctx, guild.id) 40 | .await 41 | .context("Failed to set guild application commands") 42 | { 43 | report_error(err); 44 | } 45 | } 46 | 47 | async fn interaction_create(&self, ctx: Context, interaction: Interaction) { 48 | match interaction { 49 | Interaction::ApplicationCommand(command) => { 50 | if let Err(err) = command::handler::handle(&ctx, &command) 51 | .await 52 | .context("Failed to respond to slash command") 53 | { 54 | report_error(err); 55 | } 56 | } 57 | Interaction::MessageComponent(component_interaction) => { 58 | if let Err(err) = 59 | component_interaction::handler::handle(&ctx, &component_interaction) 60 | .await 61 | .context("Failed to respond to message components interaction") 62 | { 63 | report_error(err); 64 | } 65 | } 66 | _ => {} 67 | }; 68 | } 69 | 70 | async fn message(&self, ctx: Context, msg: Message) { 71 | if let Err(err) = message::handler::handle(&ctx, msg) 72 | .await 73 | .context("Failed to handle message") 74 | { 75 | report_error(err); 76 | } 77 | } 78 | 79 | async fn voice_state_update( 80 | &self, 81 | ctx: Context, 82 | _old_voice_state: Option, 83 | new_voice_state: VoiceState, 84 | ) { 85 | if let Err(err) = voice_state::handler::handle_update(&ctx, new_voice_state.guild_id) 86 | .await 87 | .context("Failed to handle voice state update") 88 | { 89 | report_error(err); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/koe/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::error::report_error; 2 | use anyhow::{Context, Result}; 3 | use dashmap::DashMap; 4 | use koe_db::redis; 5 | use koe_speech::{speech::initialize_speakers, voicevox::VoicevoxClient}; 6 | use log::info; 7 | use sentry::integrations::anyhow::capture_anyhow; 8 | use serenity::{model::gateway::GatewayIntents, Client}; 9 | use songbird::SerenityInit; 10 | use tokio::time::Duration; 11 | 12 | mod app_state; 13 | mod command; 14 | mod component_interaction; 15 | mod error; 16 | mod event_handler; 17 | mod message; 18 | mod regex; 19 | mod voice_state; 20 | 21 | #[tokio::main] 22 | async fn main() -> Result<()> { 23 | let _guard = sentry::init(()); 24 | 25 | run().await.map_err(|err| { 26 | capture_anyhow(&err); 27 | err 28 | }) 29 | } 30 | 31 | async fn run() -> Result<()> { 32 | ecs_logger::init(); 33 | 34 | let config = koe_config::load().await?; 35 | info!("Config loaded"); 36 | 37 | let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT; 38 | 39 | let mut client = Client::builder(config.discord.bot_token, intents) 40 | .event_handler(event_handler::Handler) 41 | .application_id(config.discord.client_id) 42 | .register_songbird() 43 | .await 44 | .context("Failed to build serenity client")?; 45 | 46 | app_state::initialize( 47 | &client, 48 | app_state::AppState { 49 | redis_client: redis::Client::open(config.redis.url)?, 50 | voicevox_client: VoicevoxClient::new(config.voicevox.api_base), 51 | connected_guild_states: DashMap::new(), 52 | }, 53 | ) 54 | .await; 55 | 56 | { 57 | let d = client.data.clone(); 58 | tokio::spawn(async move { 59 | tokio::time::sleep(Duration::from_secs(3)).await; 60 | info!("Initializing speakers..."); 61 | 62 | let data = d.read().await; 63 | let state = data.get::().unwrap(); 64 | 65 | if let Err(err) = initialize_speakers(&state.voicevox_client).await { 66 | report_error(err); 67 | } 68 | }); 69 | } 70 | 71 | info!("Starting client..."); 72 | client.start().await.context("Client error occurred")?; 73 | 74 | Ok(()) 75 | } 76 | -------------------------------------------------------------------------------- /crates/koe/src/message/handler.rs: -------------------------------------------------------------------------------- 1 | use super::read::build_read_text; 2 | use crate::app_state; 3 | use anyhow::{anyhow, Context as _, Result}; 4 | use koe_db::voice::GetOption; 5 | use koe_speech::speech::{list_preset_ids, make_speech, SpeechRequest}; 6 | use log::trace; 7 | use rand::seq::SliceRandom; 8 | use serenity::{client::Context, model::channel::Message}; 9 | 10 | pub async fn handle(ctx: &Context, msg: Message) -> Result<()> { 11 | let guild_id = match msg.guild_id { 12 | Some(id) => id, 13 | None => return Ok(()), 14 | }; 15 | 16 | if !koe_call::is_connected(ctx, guild_id).await? { 17 | return Ok(()); 18 | } 19 | 20 | let state = app_state::get(ctx).await?; 21 | let mut guild_state = match state.connected_guild_states.get_mut(&guild_id) { 22 | Some(status) => status, 23 | None => return Ok(()), 24 | }; 25 | 26 | if guild_state.bound_text_channel != msg.channel_id { 27 | return Ok(()); 28 | } 29 | 30 | // Skip message from Koe itself 31 | if msg.author.id == ctx.cache.current_user_id() { 32 | return Ok(()); 33 | } 34 | 35 | // Skip message that starts with semicolon 36 | if msg.content.starts_with(';') { 37 | return Ok(()); 38 | } 39 | 40 | let mut conn = state.redis_client.get_async_connection().await?; 41 | 42 | let text = build_read_text( 43 | ctx, 44 | &mut conn, 45 | guild_id, 46 | &msg, 47 | &guild_state.last_message_read, 48 | ) 49 | .await?; 50 | trace!("Built text: {:?}", &text); 51 | 52 | if text.is_empty() { 53 | trace!("Text is empty"); 54 | return Ok(()); 55 | } 56 | 57 | let available_preset_ids = list_preset_ids(&state.voicevox_client).await?; 58 | let fallback_preset_id = available_preset_ids 59 | .choose(&mut rand::thread_rng()) 60 | .ok_or_else(|| anyhow!("No presets available"))? 61 | .into(); 62 | let preset_id = koe_db::voice::get( 63 | &mut conn, 64 | GetOption { 65 | guild_id: guild_id.into(), 66 | user_id: msg.author.id.into(), 67 | fallback: fallback_preset_id, 68 | }, 69 | ) 70 | .await? 71 | .into(); 72 | 73 | let encoded_audio = make_speech(&state.voicevox_client, SpeechRequest { text, preset_id }) 74 | .await 75 | .context("Failed to execute Text-to-Speech")?; 76 | let raw_audio = encoded_audio.decode().await?.into(); 77 | 78 | koe_call::enqueue(ctx, guild_id, raw_audio).await?; 79 | 80 | guild_state.last_message_read = Some(msg); 81 | 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /crates/koe/src/message/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handler; 2 | mod read; 3 | -------------------------------------------------------------------------------- /crates/koe/src/message/read.rs: -------------------------------------------------------------------------------- 1 | use crate::regex::{custom_emoji_regex, url_regex}; 2 | use aho_corasick::{AhoCorasickBuilder, MatchKind}; 3 | use anyhow::Result; 4 | use discord_md::generate::{ToMarkdownString, ToMarkdownStringOption}; 5 | use koe_db::{dict::GetAllOption, redis}; 6 | use serenity::{ 7 | client::Context, 8 | model::{channel::Message, id::GuildId}, 9 | utils::ContentSafeOptions, 10 | }; 11 | 12 | pub async fn build_read_text( 13 | ctx: &Context, 14 | conn: &mut redis::aio::Connection, 15 | guild_id: GuildId, 16 | msg: &Message, 17 | last_msg: &Option, 18 | ) -> Result { 19 | let author_name = build_author_name(ctx, msg).await; 20 | 21 | let content = plain_content(ctx, msg); 22 | let content = replace_custom_emojis(&content); 23 | let content = discord_md::parse(&content).to_markdown_string( 24 | &ToMarkdownStringOption::new() 25 | .omit_format(true) 26 | .omit_spoiler(true), 27 | ); 28 | let content = remove_url(&content); 29 | 30 | let text = if should_read_author_name(msg, last_msg) { 31 | format!("{}。{}", author_name, content) 32 | } else { 33 | content 34 | }; 35 | 36 | let text = replace_words_on_dict(conn, guild_id, &text).await?; 37 | 38 | // 文字数を60文字に制限 39 | if text.chars().count() > 60 { 40 | Ok(text.chars().take(60 - 4).collect::() + "、以下略") 41 | } else { 42 | Ok(text) 43 | } 44 | } 45 | 46 | fn should_read_author_name(msg: &Message, last_msg: &Option) -> bool { 47 | let last_msg = match last_msg { 48 | Some(msg) => msg, 49 | None => return true, 50 | }; 51 | 52 | msg.author != last_msg.author 53 | || (msg.timestamp.unix_timestamp() - last_msg.timestamp.unix_timestamp()) > 10 54 | } 55 | 56 | async fn build_author_name(ctx: &Context, msg: &Message) -> String { 57 | msg.author_nick(&ctx.http) 58 | .await 59 | // FIXME: `User::name`はユーザーの表示名ではなく一意のユーザー名を返す。現在のSerenityの実装では、ユーザーの表示名を取得する方法がない。 60 | // cf. https://github.com/serenity-rs/serenity/discussions/2500 61 | .unwrap_or_else(|| msg.author.name.clone()) 62 | } 63 | 64 | /// [Message]の内容を返す。ID表記されたメンションやチャンネル名は読める形に書き換える。 65 | fn plain_content(ctx: &Context, msg: &Message) -> String { 66 | let mut options = ContentSafeOptions::new() 67 | .clean_channel(true) 68 | .clean_role(true) 69 | .clean_user(true) 70 | .show_discriminator(false) 71 | .clean_here(false) 72 | .clean_everyone(false); 73 | 74 | if let Some(guild_id) = msg.guild_id { 75 | options = options.display_as_member_from(guild_id); 76 | } 77 | 78 | serenity::utils::content_safe(&ctx.cache, &msg.content, &options, &msg.mentions) 79 | } 80 | 81 | /// カスタム絵文字を読める形に置き換える 82 | fn replace_custom_emojis(text: &str) -> String { 83 | custom_emoji_regex().replace_all(text, "$1").into() 84 | } 85 | 86 | async fn replace_words_on_dict( 87 | conn: &mut redis::aio::Connection, 88 | guild_id: GuildId, 89 | text: &str, 90 | ) -> Result { 91 | let dict = koe_db::dict::get_all( 92 | conn, 93 | GetAllOption { 94 | guild_id: guild_id.into(), 95 | }, 96 | ) 97 | .await?; 98 | 99 | let word_list = dict.iter().map(|(word, _)| word).collect::>(); 100 | let read_as_list = dict.iter().map(|(_, read_as)| read_as).collect::>(); 101 | 102 | let ac = AhoCorasickBuilder::new() 103 | .match_kind(MatchKind::LeftmostLongest) 104 | .build(word_list)?; 105 | 106 | Ok(ac.replace_all(text, &read_as_list)) 107 | } 108 | 109 | /// メッセージのURLを除去 110 | fn remove_url(text: &str) -> String { 111 | url_regex().replace_all(text, "、").into() 112 | } 113 | -------------------------------------------------------------------------------- /crates/koe/src/regex.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | // https://docs.rs/once_cell/latest/once_cell/#lazily-compiled-regex 4 | macro_rules! regex { 5 | ($re:literal $(,)?) => {{ 6 | static RE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); 7 | RE.get_or_init(|| regex::Regex::new($re).unwrap()) 8 | }}; 9 | } 10 | 11 | pub fn url_regex() -> &'static Regex { 12 | regex!(r"https?://\S\S+") 13 | } 14 | 15 | pub fn custom_emoji_regex() -> &'static Regex { 16 | regex!(r"<(:\w+:)\d+>") 17 | } 18 | -------------------------------------------------------------------------------- /crates/koe/src/voice_state/handler.rs: -------------------------------------------------------------------------------- 1 | use crate::app_state; 2 | use anyhow::{Context as _, Result}; 3 | use log::debug; 4 | use serenity::{ 5 | client::Context, 6 | model::id::{ChannelId, GuildId, UserId}, 7 | }; 8 | 9 | pub async fn handle_update(ctx: &Context, guild_id: Option) -> Result<()> { 10 | let guild_id = match guild_id { 11 | Some(id) => id, 12 | None => return Ok(()), 13 | }; 14 | 15 | let current_voice_channel_id = match get_current_voice_channel_id(ctx, guild_id)? { 16 | Some(id) => id, 17 | None => return Ok(()), 18 | }; 19 | 20 | let current_channel_user_list = 21 | list_users_in_voice_channel(ctx, guild_id, current_voice_channel_id) 22 | .context("Failed to count the number of users in the bot's channel")?; 23 | 24 | // VCのメンバーがKoe自身のみになった場合は抜ける 25 | if current_channel_user_list.len() == 1 { 26 | koe_call::leave(ctx, guild_id) 27 | .await 28 | .context("Failed to leave voice channel")?; 29 | 30 | let state = app_state::get(ctx).await?; 31 | state.connected_guild_states.remove(&guild_id); 32 | 33 | debug!("Automatically disconnected in guild {}", guild_id.as_u64()); 34 | } 35 | 36 | Ok(()) 37 | } 38 | 39 | fn get_current_voice_channel_id(ctx: &Context, guild_id: GuildId) -> Result> { 40 | let current_user_id = ctx.cache.current_user_id(); 41 | 42 | let voice_state_map = guild_id 43 | .to_guild_cached(&ctx.cache) 44 | .context("Failed to find guild in the cache")? 45 | .voice_states; 46 | 47 | let current_voice_state = match voice_state_map.get(¤t_user_id) { 48 | Some(state) => state, 49 | None => return Ok(None), 50 | }; 51 | 52 | Ok(current_voice_state.channel_id) 53 | } 54 | 55 | fn list_users_in_voice_channel( 56 | ctx: &Context, 57 | guild_id: GuildId, 58 | channel_id: ChannelId, 59 | ) -> Result> { 60 | let voice_state_map = guild_id 61 | .to_guild_cached(&ctx.cache) 62 | .context("Failed to find guild in the cache")? 63 | .voice_states; 64 | 65 | let list = voice_state_map 66 | .into_iter() 67 | .filter(|(_, state)| state.channel_id == Some(channel_id)) 68 | .map(|(_, state)| state.user_id) 69 | .collect(); 70 | 71 | Ok(list) 72 | } 73 | -------------------------------------------------------------------------------- /crates/koe/src/voice_state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handler; 2 | -------------------------------------------------------------------------------- /deployment/config/koe.yaml: -------------------------------------------------------------------------------- 1 | discord: 2 | client_id: YOUR_CLIENT_ID 3 | bot_token: YOUR_BOT_TOKEN 4 | 5 | voicevox: 6 | api_base: http://voicevox:50021 7 | 8 | redis: 9 | url: redis://:YOUR_STRONG_PASSWORD@redis 10 | -------------------------------------------------------------------------------- /deployment/config/redis.conf: -------------------------------------------------------------------------------- 1 | # Set password 2 | requirepass YOUR_STRONG_PASSWORD 3 | 4 | # Disable CONFIG command 5 | rename-command CONFIG "" 6 | 7 | # Write to RDB every minute 8 | save 60 1 9 | -------------------------------------------------------------------------------- /deployment/config/voicevox_presets.yaml: -------------------------------------------------------------------------------- 1 | - id: 0 2 | name: 四国めたん ノーマル 3 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 4 | style_id: 2 5 | speedScale: 1.2 6 | pitchScale: 0 7 | intonationScale: 1 8 | volumeScale: 1 9 | prePhonemeLength: 0.1 10 | postPhonemeLength: 0.1 11 | 12 | - id: 12 13 | name: 四国めたん あまあま 14 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 15 | style_id: 0 16 | speedScale: 1.2 17 | pitchScale: 0 18 | intonationScale: 1 19 | volumeScale: 1 20 | prePhonemeLength: 0.1 21 | postPhonemeLength: 0.1 22 | 23 | - id: 13 24 | name: 四国めたん ツンツン 25 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 26 | style_id: 6 27 | speedScale: 1.2 28 | pitchScale: 0 29 | intonationScale: 1 30 | volumeScale: 1 31 | prePhonemeLength: 0.1 32 | postPhonemeLength: 0.1 33 | 34 | - id: 14 35 | name: 四国めたん セクシー 36 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 37 | style_id: 4 38 | speedScale: 1.2 39 | pitchScale: 0 40 | intonationScale: 1 41 | volumeScale: 1 42 | prePhonemeLength: 0.1 43 | postPhonemeLength: 0.1 44 | 45 | - id: 1 46 | name: ずんだもん ノーマル 47 | speaker_uuid: 388f246b-8c41-4ac1-8e2d-5d79f3ff56d9 48 | style_id: 3 49 | speedScale: 1.2 50 | pitchScale: 0 51 | intonationScale: 1 52 | volumeScale: 1 53 | prePhonemeLength: 0.1 54 | postPhonemeLength: 0.1 55 | 56 | - id: 15 57 | name: ずんだもん あまあま 58 | speaker_uuid: 388f246b-8c41-4ac1-8e2d-5d79f3ff56d9 59 | style_id: 1 60 | speedScale: 1.2 61 | pitchScale: 0 62 | intonationScale: 1 63 | volumeScale: 1 64 | prePhonemeLength: 0.1 65 | postPhonemeLength: 0.1 66 | 67 | - id: 16 68 | name: ずんだもん ツンツン 69 | speaker_uuid: 388f246b-8c41-4ac1-8e2d-5d79f3ff56d9 70 | style_id: 7 71 | speedScale: 1.2 72 | pitchScale: 0 73 | intonationScale: 1 74 | volumeScale: 1 75 | prePhonemeLength: 0.1 76 | postPhonemeLength: 0.1 77 | 78 | - id: 17 79 | name: ずんだもん セクシー 80 | speaker_uuid: 388f246b-8c41-4ac1-8e2d-5d79f3ff56d9 81 | style_id: 5 82 | speedScale: 1.2 83 | pitchScale: 0 84 | intonationScale: 1 85 | volumeScale: 1 86 | prePhonemeLength: 0.1 87 | postPhonemeLength: 0.1 88 | 89 | - id: 18 90 | name: ずんだもん ささやき 91 | speaker_uuid: 388f246b-8c41-4ac1-8e2d-5d79f3ff56d9 92 | style_id: 22 93 | speedScale: 1.2 94 | pitchScale: 0 95 | intonationScale: 1 96 | volumeScale: 1 97 | prePhonemeLength: 0.1 98 | postPhonemeLength: 0.1 99 | 100 | - id: 2 101 | name: 春日部つむぎ 102 | speaker_uuid: 35b2c544-660e-401e-b503-0e14c635303a 103 | style_id: 8 104 | speedScale: 1.2 105 | pitchScale: 0 106 | intonationScale: 1 107 | volumeScale: 1 108 | prePhonemeLength: 0.1 109 | postPhonemeLength: 0.1 110 | 111 | - id: 3 112 | name: 雨晴はう 113 | speaker_uuid: 3474ee95-c274-47f9-aa1a-8322163d96f1 114 | style_id: 10 115 | speedScale: 1.2 116 | pitchScale: 0 117 | intonationScale: 1 118 | volumeScale: 1 119 | prePhonemeLength: 0.1 120 | postPhonemeLength: 0.1 121 | 122 | - id: 4 123 | name: 波音リツ 124 | speaker_uuid: b1a81618-b27b-40d2-b0ea-27a9ad408c4b 125 | style_id: 9 126 | speedScale: 1.2 127 | pitchScale: 0 128 | intonationScale: 1 129 | volumeScale: 1 130 | prePhonemeLength: 0.1 131 | postPhonemeLength: 0.1 132 | 133 | - id: 5 134 | name: 玄野武宏 135 | speaker_uuid: c30dc15a-0992-4f8d-8bb8-ad3b314e6a6f 136 | style_id: 11 137 | speedScale: 1.2 138 | pitchScale: 0 139 | intonationScale: 1 140 | volumeScale: 1 141 | prePhonemeLength: 0.1 142 | postPhonemeLength: 0.1 143 | 144 | - id: 6 145 | name: 白上虎太郎 146 | speaker_uuid: e5020595-5c5d-4e87-b849-270a518d0dcf 147 | style_id: 12 148 | speedScale: 1.2 149 | pitchScale: 0 150 | intonationScale: 1 151 | volumeScale: 1 152 | prePhonemeLength: 0.1 153 | postPhonemeLength: 0.1 154 | 155 | - id: 7 156 | name: 青山龍星 157 | speaker_uuid: 4f51116a-d9ee-4516-925d-21f183e2afad 158 | style_id: 13 159 | speedScale: 1.2 160 | pitchScale: 0 161 | intonationScale: 1 162 | volumeScale: 1 163 | prePhonemeLength: 0.1 164 | postPhonemeLength: 0.1 165 | 166 | - id: 8 167 | name: 冥鳴ひまり 168 | speaker_uuid: 8eaad775-3119-417e-8cf4-2a10bfd592c8 169 | style_id: 14 170 | speedScale: 1.2 171 | pitchScale: 0 172 | intonationScale: 1 173 | volumeScale: 1 174 | prePhonemeLength: 0.1 175 | postPhonemeLength: 0.1 176 | 177 | - id: 9 178 | name: 九州そら ノーマル 179 | speaker_uuid: 481fb609-6446-4870-9f46-90c4dd623403 180 | style_id: 16 181 | speedScale: 1.5 182 | pitchScale: 0 183 | intonationScale: 1 184 | volumeScale: 1 185 | prePhonemeLength: 0.1 186 | postPhonemeLength: 0.1 187 | 188 | - id: 19 189 | name: 九州そら あまあま 190 | speaker_uuid: 481fb609-6446-4870-9f46-90c4dd623403 191 | style_id: 15 192 | speedScale: 1.5 193 | pitchScale: 0 194 | intonationScale: 1 195 | volumeScale: 1 196 | prePhonemeLength: 0.1 197 | postPhonemeLength: 0.1 198 | 199 | - id: 20 200 | name: 九州そら ツンツン 201 | speaker_uuid: 481fb609-6446-4870-9f46-90c4dd623403 202 | style_id: 18 203 | speedScale: 1.5 204 | pitchScale: 0 205 | intonationScale: 1 206 | volumeScale: 1 207 | prePhonemeLength: 0.1 208 | postPhonemeLength: 0.1 209 | 210 | - id: 21 211 | name: 九州そら セクシー 212 | speaker_uuid: 481fb609-6446-4870-9f46-90c4dd623403 213 | style_id: 17 214 | speedScale: 1.5 215 | pitchScale: 0 216 | intonationScale: 1 217 | volumeScale: 1 218 | prePhonemeLength: 0.1 219 | postPhonemeLength: 0.1 220 | 221 | - id: 22 222 | name: 九州そら ささやき 223 | speaker_uuid: 481fb609-6446-4870-9f46-90c4dd623403 224 | style_id: 19 225 | speedScale: 1.5 226 | pitchScale: 0 227 | intonationScale: 1 228 | volumeScale: 1 229 | prePhonemeLength: 0.1 230 | postPhonemeLength: 0.1 231 | 232 | - id: 10 233 | name: もち子さん 234 | speaker_uuid: 9f3ee141-26ad-437e-97bd-d22298d02ad2 235 | style_id: 20 236 | speedScale: 1.2 237 | pitchScale: 0 238 | intonationScale: 1 239 | volumeScale: 1 240 | prePhonemeLength: 0.1 241 | postPhonemeLength: 0.1 242 | 243 | - id: 11 244 | name: 剣崎雌雄 245 | speaker_uuid: 1a17ca16-7ee5-4ea5-b191-2f02ace24d21 246 | style_id: 21 247 | speedScale: 1.2 248 | pitchScale: 0 249 | intonationScale: 1 250 | volumeScale: 1 251 | prePhonemeLength: 0.1 252 | postPhonemeLength: 0.1 253 | -------------------------------------------------------------------------------- /deployment/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: ghcr.io/ciffelia/koe:1.1.1 4 | init: true 5 | depends_on: 6 | redis: 7 | condition: service_healthy 8 | voicevox: 9 | condition: service_healthy 10 | restart: unless-stopped 11 | environment: 12 | RUST_LOG: warn 13 | volumes: 14 | - "./config/koe.yaml:/etc/koe.yaml:ro" 15 | 16 | redis: 17 | image: redis:7.2.5 18 | command: /usr/local/etc/redis/redis.conf 19 | restart: unless-stopped 20 | expose: 21 | - 6379 22 | volumes: 23 | - "./config/redis.conf:/usr/local/etc/redis/redis.conf:ro" 24 | - "redis-data:/data" 25 | healthcheck: 26 | test: ["CMD", "redis-cli", "ping"] 27 | interval: 10s 28 | timeout: 3s 29 | start_period: 1m 30 | start_interval: 3s 31 | 32 | voicevox: 33 | image: voicevox/voicevox_engine:cpu-ubuntu20.04-0.14.6 34 | restart: unless-stopped 35 | expose: 36 | - 50021 37 | volumes: 38 | - "./config/voicevox_presets.yaml:/opt/voicevox_engine/presets.yaml:ro" 39 | healthcheck: 40 | test: ["CMD", "wget", "--quiet", "-O", "/dev/null", "http://localhost:50021/version"] 41 | interval: 10s 42 | timeout: 3s 43 | start_period: 1m 44 | start_interval: 3s 45 | 46 | volumes: 47 | redis-data: 48 | -------------------------------------------------------------------------------- /devtools/.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/releases/** binary 2 | /.yarn/plugins/** binary 3 | -------------------------------------------------------------------------------- /devtools/.gitignore: -------------------------------------------------------------------------------- 1 | /.yarn/* 2 | !/.yarn/patches 3 | !/.yarn/plugins 4 | !/.yarn/releases 5 | !/.yarn/sdks 6 | !/.yarn/versions 7 | /.pnp.* 8 | /node_modules 9 | -------------------------------------------------------------------------------- /devtools/.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | MSG_FILE=$(realpath "$1") 5 | 6 | cd devtools 7 | 8 | yarn run commitlint --edit "$MSG_FILE" 9 | -------------------------------------------------------------------------------- /devtools/.prettierignore: -------------------------------------------------------------------------------- 1 | # Yarn 2 | /.yarn 3 | /.yarnrc.yml 4 | /.pnp.* 5 | 6 | /template 7 | -------------------------------------------------------------------------------- /devtools/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | singleQuote: true, 4 | }; 5 | -------------------------------------------------------------------------------- /devtools/.release-it.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const changelogHeaderTemplate = fs.readFileSync( 5 | path.join(__dirname, './template/changelog/header.hbs'), 6 | 'utf-8', 7 | ); 8 | 9 | module.exports = { 10 | git: { 11 | commitMessage: 'chore: release v${version}', 12 | }, 13 | npm: { 14 | publish: false, 15 | }, 16 | github: { 17 | release: true, 18 | releaseName: 'v${version}', 19 | assets: ['./koe_*.zip'], 20 | }, 21 | plugins: { 22 | '@release-it/conventional-changelog': { 23 | preset: 'conventionalcommits', 24 | writerOpts: { 25 | headerPartial: changelogHeaderTemplate, 26 | }, 27 | }, 28 | }, 29 | hooks: { 30 | 'after:bump': 31 | "sed -i 's/koe:${latestVersion}/koe:${version}/g' ../deployment/docker-compose.yml", 32 | 'before:git:release': 'git add ../deployment/docker-compose.yml', 33 | 'before:github:release': 34 | "cp -r ../deployment ./koe && zip -r 'koe_${version}.zip' ./koe && rm -rf ./koe", 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /devtools/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | yarnPath: .yarn/releases/yarn-4.2.2.cjs 4 | -------------------------------------------------------------------------------- /devtools/commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /devtools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koe", 3 | "version": "1.1.1", 4 | "private": true, 5 | "scripts": { 6 | "lint": "prettier --ignore-path=.prettierignore --check .", 7 | "fix": "prettier --ignore-path=.prettierignore --write .", 8 | "print-next-version": "release-it --release-version", 9 | "create-release": "release-it --ci", 10 | "generate-docker-tags": "node ./src/generateDockerTags.js", 11 | "postinstall": "cd .. && husky install ./devtools/.husky" 12 | }, 13 | "packageManager": "yarn@4.2.2", 14 | "dependencies": { 15 | "@commitlint/cli": "^18.6.1", 16 | "@commitlint/config-conventional": "^18.6.3", 17 | "@release-it/conventional-changelog": "^8.0.2", 18 | "conventional-changelog-conventionalcommits": "^7.0.2", 19 | "husky": "^8.0.3", 20 | "release-it": "^17.3.0", 21 | "semver": "^7.6.3" 22 | }, 23 | "devDependencies": { 24 | "prettier": "^3.3.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /devtools/src/generateDockerTags.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver'); 2 | const pkg = require('../package.json'); 3 | 4 | const main = () => { 5 | const { version } = pkg; 6 | 7 | const parsed = semver.parse(version); 8 | if (parsed === null) { 9 | console.error(`version ${version} is invalid`); 10 | process.exit(1); 11 | } 12 | 13 | console.log(generateTags(parsed).join(' ')); 14 | }; 15 | 16 | const generateTags = (v) => { 17 | if (v.prerelease.length !== 0) { 18 | return [v.version]; 19 | } 20 | 21 | if (v.major === 0) { 22 | return [ 23 | `${v.major}.${v.minor}.${v.patch}`, 24 | `${v.major}.${v.minor}`, 25 | 'latest', 26 | ]; 27 | } 28 | 29 | return [ 30 | `${v.major}.${v.minor}.${v.patch}`, 31 | `${v.major}.${v.minor}`, 32 | `${v.major}`, 33 | 'latest', 34 | ]; 35 | }; 36 | 37 | main(); 38 | -------------------------------------------------------------------------------- /devtools/template/changelog/header.hbs: -------------------------------------------------------------------------------- 1 | ### インストール 2 | 3 | [セットアップガイド](https://github.com/ciffelia/koe/blob/{{version}}/docs/setup_guide.md)をご覧ください。 4 | -------------------------------------------------------------------------------- /docs/logo/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciffelia/koe/bc97650dbe0f044d11cfc893ddc58c20724aa8a3/docs/logo/icon.png -------------------------------------------------------------------------------- /docs/logo/icon.svg: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 13 | 17 | 21 | 25 | 29 | 33 | 37 | 41 | 45 | 46 | 56 | K 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciffelia/koe/bc97650dbe0f044d11cfc893ddc58c20724aa8a3/docs/logo/logo.png -------------------------------------------------------------------------------- /docs/logo/logo.svg: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 13 | 17 | 21 | 25 | 29 | 33 | 37 | 41 | 45 | 46 | 56 | Koe 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/release_procedure.md: -------------------------------------------------------------------------------- 1 | # リリース手順 2 | 3 | 1. `main`ブランチの最新コミットで CI が通っていることを確認します。 4 | 2. [Create release](https://github.com/ciffelia/koe/actions/workflows/release.yml) を開き、`Run workflow` をクリックしてワークフローを実行します。 5 | 3. 自動で GitHub Release が作成され、Docker Tag が Container Registry にプッシュされます。 6 | 4. GitHub Release のリリースノートを編集します。 7 | -------------------------------------------------------------------------------- /docs/setup_guide.md: -------------------------------------------------------------------------------- 1 | # Koe セットアップガイド 2 | 3 | この文章では Koe を起動するための手順を説明します。 4 | 5 | ## 0. システム要件 6 | 7 | ### 0-1. ハードウェア 8 | 9 | 読み上げ音声の合成は非常に負荷の大きい処理です。Koe を実行するコンピュータの性能が低い場合、テキストチャンネルにメッセージが送信されてからボイスチャンネルで読み上げられるまでの遅延が大きくなります。 10 | 11 | Koe が使用している音声合成エンジンである VOICEVOX ENGINE では、音声合成処理に CPU または GPU を使用することができます。Bot を快適に使用するには高性能な CPU または GPU と 2GB 以上のメモリを搭載したマシンが必要です。 12 | 13 | 参考までに、[@ciffelia](https://github.com/ciffelia) が使用しているマシンでの遅延は以下の通りです。 14 | 15 | - CPU: Ryzen 5 5600X: 1 秒程度 16 | - CPU: Raspberry Pi 4 (8GB): 15 秒程度 17 | - GPU: RTX 3070: 1 秒程度 18 | 19 | ※起動直後はモデルの初期化処理が行われているため、遅延がより大きくなります。 20 | 21 | ### 0-2. ソフトウェア 22 | 23 | Koe の実行には Docker および Docker Compose が必要です。あらかじめインストールしておいてください。なお、Koe が動作するには Redis と VOICEVOX ENGINE が必要ですが、これらは Docker Compose を用いて起動するため事前のインストールは不要です。 24 | 25 | ## 1. Discord Bot の登録 26 | 27 | ### 1-1. アプリケーションの作成 28 | 29 | 1. [Discord Developer Portal](https://discord.com/developers/applications) を開き、新しくアプリケーションを作成します。 30 | 2. General Information ページに記載されている Application ID (Client ID) を控えておきます。 31 | 3. Description に VOICEVOX や各音源のクレジット、使用上の注意事項などを入力します。ここで記入した内容は Bot のプロフィールに表示されます。 32 | 4. Bot ページに移動し、Add Bot をクリックして Bot を有効にします。 33 | 5. Message Content Intent を有効にします。 34 | 6. Reset Token をクリックして Token を生成し、控えておきます。 35 | 36 | ### 1-2. サーバーに Bot を追加 37 | 38 | 以下の URL にアクセスしてサーバーに Bot を追加します。URL の`CLIENT_ID`は先ほど控えた Application ID に置き換えてください。 39 | 40 | ``` 41 | https://discord.com/api/oauth2/authorize?client_id=CLIENT_ID&permissions=3146752&scope=bot%20applications.commands 42 | ``` 43 | 44 |
45 | 参考: Koe が使用する権限 46 | 47 | - OAuth2 Scopes 48 | - `application.commands` 49 | - `bot` 50 | - Bot Permissions 51 | - General Permissions 52 | - View Channels 53 | - Voice Permissions 54 | - Connect 55 | - Speak 56 |
57 | 58 | ## 2. 設定ファイルの準備 59 | 60 | ### 2-1. 設定ファイルのダウンロード 61 | 62 | 1. [最新のリリース](https://github.com/ciffelia/koe/releases/latest)を開き、`koe_x.x.x.zip`をダウンロードします。 63 | 2. ダウンロードしたアーカイブを展開します。以後、このディレクトリの中で作業を行います。 64 | 65 | ### 2-2. Redis のパスワード設定 66 | 67 | 1. `config/redis.conf`をテキストエディタで開きます。 68 | 2. `YOUR_STRONG_PASSWORD` を適当なパスワードに変更します。 69 | 70 | ### 2-3. VOICEVOX ENGINE のプリセット設定(任意) 71 | 72 | 1. `config/voicevox_presets.yaml`をテキストエディタで開きます。 73 | 2. 必要に応じてプリセットを変更します。 74 | 75 | ### 2-4. Koe の設定 76 | 77 | 1. `config/koe.yaml`をテキストエディタで開きます。 78 | 2. 次の設定を書き換えます。 79 | - `discord.client_id`: 1-1 で控えた Client ID 80 | - `discord.bot_token`: 1-1 で控えた Bot Token 81 | - `voicevox.api_base`: VOICEVOX ENGINE の URL 82 | - Docker Compose を使用する場合はデフォルトのままで問題ありません。 83 | - `redis.url`: Redis に接続するための URL 84 | - 形式は `redis://[][:@][:port][/]` です。 85 | - Docker Compose を使用する場合は`YOUR_STRONG_PASSWORD`を Redis のパスワードに置き換えるのみで問題ありません。 86 | - 詳細は https://docs.rs/redis#connection-parameters をご確認ください。 87 | 88 | ### 2-5. 環境変数の設定(任意) 89 | 90 | `docker-compose.yml` から下記の環境変数を設定することができます。いずれも原則として設定する必要はありませんが、デバッグ時に役立ちます。 91 | 92 | - `KOE_CONFIG`: 設定ファイルの場所 93 | - デフォルトでは `/etc/koe.yaml` となっています。 94 | - `RUST_LOG`: ログレベル 95 | - `koe`に設定すると詳細なログが出力されます。 96 | - 詳細は https://docs.rs/env_logger#enabling-logging をご確認ください。 97 | - `SENTRY_DSN`: Sentry の DSN 98 | - 設定するとエラーを Sentry に送信することができます。 99 | 100 | ## 3. 起動 101 | 102 | 下記のコマンドで開始・停止等の操作を行うことができます。詳細は https://docs.docker.com/compose/ をご確認ください。 103 | 104 | - `docker compose up --detach` 105 | - Koe, Redis, VOICEVOX ENGINE を起動します。 106 | - `docker compose logs` 107 | - ログを確認します。 108 | - `docker compose down` 109 | - Koe, Redis, VOICEVOX ENGINE を停止します。 110 | - `docker compose down --volumes` 111 | - Koe, Redis, VOICEVOX ENGINE を停止し、Redis に保存されている設定をすべて削除します。 112 | - `docker compose pull` 113 | - コンテナイメージを更新します。 114 | 115 | --- 116 | 117 | 不明な点がありましたら[Discussions](https://github.com/ciffelia/koe/discussions)でご相談ください。 118 | -------------------------------------------------------------------------------- /docs/user_guide.md: -------------------------------------------------------------------------------- 1 | # Koe の使い方(コマンド一覧) 2 | 3 | Koe はテキストチャンネルに送信されたコマンドによって動作します。 4 | 5 | ## 読み上げ開始: `/join`, `/kjoin` 6 | 7 | - VC に接続した状態で、読み上げたいテキストチャンネルで`/join`を送信すると、Bot が入室し読み上げを開始します。 8 | - `/join`を送信したチャンネルの新規メッセージが読み上げられます。 9 | - `/join`の代わりに`/kjoin`を使うこともできます。 10 | - サーバーに複数の Bot が存在していて、コマンドが重複しているときに便利です。 11 | 12 | ## 読み上げ終了: `/leave`, `/kleave` 13 | 14 | - テキストチャンネルで`/leave`を送信すると、Bot が退室します。 15 | - どのチャンネルでも使えます。 16 | - VC に接続していないメンバーでも使えます。 17 | - `/leave`の代わりに`/kleave`を使うこともできます。 18 | - サーバーに複数の Bot が存在していて、コマンドが重複しているときに便利です。 19 | - 全員が VC から退室すると、Bot も自動的に退室します。 20 | 21 | ## 読み上げ中のメッセージをスキップ: `/skip`, `/kskip` 22 | 23 | - `/skip`を送信すると、現在読み上げているメッセージの読み上げを中止して、次のメッセージを読み上げます。 24 | - `/skip`の代わりに`/kskip`を使うこともできます。 25 | - サーバーに複数の Bot が存在していて、コマンドが重複しているときに便利です。 26 | 27 | ## 声を設定: `/voice` 28 | 29 | - `/voice`を送信すると、あなたのメッセージを読み上げる際に使用する音源を設定するドロップダウンリストが表示されます。 30 | - 設定はメンバーごとに保存されます。また、メンバーはサーバーごとに異なる音源を設定できます。 31 | - はじめはメンバーごとにランダムな音源が割り当てられています。 32 | 33 | ## 辞書を閲覧・編集: `/dict` 34 | 35 | - あらかじめ、特定の語句に別の読み方を設定しておくことができます。これを辞書機能といいます。 36 | - 辞書はサーバーごとに設定できます。1 つのサーバーに 1 冊の辞書です。 37 | - `/dict add 読み方を設定したい語句 読み方`を送信すると、辞書に語句を追加します。 38 | - `/dict remove 語句`を送信すると、辞書から語句を削除します。 39 | - `/dict view`を送信すると、辞書全体を表示します。 40 | 41 | ## 使い方を表示: `/help` 42 | 43 | - このページの URL を表示します。 44 | 45 | ## 補足: 読み上げの仕組み 46 | 47 | 1. `/join`を送信したチャンネルでのメッセージを受信 48 | 2. スポイラー(ネタバレ、伏せ字)を削除 49 | 3. メッセージの送信者名と内容それぞれから URL を削除 50 | 4. 送信者名と内容を結合 51 | - ただし、同一メンバーによる 10 秒以内の連続したメッセージの場合は、名前は省略する 52 | 5. 辞書に登録されている語句を読み替え 53 | 6. 文字数が 60 文字を超えた場合、56 文字目以降は切り捨て、「以下略」を末尾に追加 54 | --------------------------------------------------------------------------------