├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── api ├── .gitignore ├── gradle │ └── dependency-locks │ │ ├── annotationProcessor.lockfile │ │ ├── archives.lockfile │ │ ├── compile.lockfile │ │ ├── compileClasspath.lockfile │ │ ├── compileOnly.lockfile │ │ ├── default.lockfile │ │ ├── jacocoAgent.lockfile │ │ ├── jacocoAnt.lockfile │ │ ├── runtime.lockfile │ │ ├── runtimeClasspath.lockfile │ │ ├── testAnnotationProcessor.lockfile │ │ ├── testCompile.lockfile │ │ ├── testCompileClasspath.lockfile │ │ ├── testCompileOnly.lockfile │ │ ├── testRuntime.lockfile │ │ └── testRuntimeClasspath.lockfile └── src │ ├── main │ └── java │ │ ├── net │ │ └── dv8tion │ │ │ └── jda │ │ │ └── api │ │ │ └── audio │ │ │ ├── AudioSendHandler.java │ │ │ ├── factory │ │ │ ├── IAudioSendFactory.java │ │ │ ├── IAudioSendSystem.java │ │ │ └── IPacketProvider.java │ │ │ └── hooks │ │ │ └── ConnectionStatus.java │ │ └── space │ │ └── npstr │ │ └── magma │ │ └── api │ │ ├── MagmaApi.java │ │ ├── MdcKey.java │ │ ├── Member.java │ │ ├── ServerUpdate.java │ │ ├── SpeakingMode.java │ │ ├── WebsocketConnectionState.java │ │ ├── event │ │ ├── ImmutableApiEvent.java │ │ ├── MagmaEvent.java │ │ ├── WebSocketClosed.java │ │ └── package-info.java │ │ └── package-info.java │ └── test │ └── java │ └── space │ └── npstr │ └── magma │ └── api │ ├── MemberTest.java │ └── ServerUpdateTest.java ├── build.gradle ├── gradle ├── dependency-locks │ ├── annotationProcessor.lockfile │ ├── archives.lockfile │ ├── buildscript-classpath.lockfile │ ├── compile.lockfile │ ├── compileClasspath.lockfile │ ├── compileOnly.lockfile │ ├── default.lockfile │ ├── jacocoAgent.lockfile │ ├── jacocoAnt.lockfile │ ├── runtime.lockfile │ ├── runtimeClasspath.lockfile │ ├── testAnnotationProcessor.lockfile │ ├── testCompile.lockfile │ ├── testCompileClasspath.lockfile │ ├── testCompileOnly.lockfile │ ├── testRuntime.lockfile │ └── testRuntimeClasspath.lockfile └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── impl ├── .gitignore ├── gradle │ └── dependency-locks │ │ ├── annotationProcessor.lockfile │ │ ├── archives.lockfile │ │ ├── compile.lockfile │ │ ├── compileClasspath.lockfile │ │ ├── compileOnly.lockfile │ │ ├── default.lockfile │ │ ├── jacocoAgent.lockfile │ │ ├── jacocoAnt.lockfile │ │ ├── runtime.lockfile │ │ ├── runtimeClasspath.lockfile │ │ ├── testAnnotationProcessor.lockfile │ │ ├── testCompile.lockfile │ │ ├── testCompileClasspath.lockfile │ │ ├── testCompileOnly.lockfile │ │ ├── testRuntime.lockfile │ │ └── testRuntimeClasspath.lockfile └── src │ ├── main │ └── java │ │ ├── com │ │ └── iwebpp │ │ │ └── crypto │ │ │ └── TweetNaclFast.java │ │ ├── net │ │ └── dv8tion │ │ │ └── jda │ │ │ └── api │ │ │ └── audio │ │ │ └── AudioPacket.java │ │ └── space │ │ └── npstr │ │ └── magma │ │ └── impl │ │ ├── AudioStack.java │ │ ├── AudioStackLifecyclePipeline.java │ │ ├── EncryptionMode.java │ │ ├── Magma.java │ │ ├── MagmaVersionProvider.java │ │ ├── connections │ │ ├── AudioConnection.java │ │ ├── AudioWebSocket.java │ │ ├── AudioWebSocketSessionHandler.java │ │ ├── hax │ │ │ ├── ClosingReactorNettyWebSocketClient.java │ │ │ ├── ClosingReactorNettyWebSocketSession.java │ │ │ ├── ClosingWebSocketClient.java │ │ │ └── package-info.java │ │ └── package-info.java │ │ ├── events │ │ ├── audio │ │ │ ├── conn │ │ │ │ ├── ConnectionEvent.java │ │ │ │ ├── SetEncryptionMode.java │ │ │ │ ├── SetSecretKey.java │ │ │ │ ├── SetSsrc.java │ │ │ │ ├── SetTargetAddress.java │ │ │ │ ├── Shutdown.java │ │ │ │ ├── UpdateSendHandler.java │ │ │ │ ├── UpdateSpeaking.java │ │ │ │ └── package-info.java │ │ │ ├── lifecycle │ │ │ │ ├── CloseWebSocket.java │ │ │ │ ├── ConnectWebSocket.java │ │ │ │ ├── LifecycleEvent.java │ │ │ │ ├── Shutdown.java │ │ │ │ ├── UpdateSendHandler.java │ │ │ │ ├── UpdateSpeakingMode.java │ │ │ │ ├── VoiceServerUpdate.java │ │ │ │ └── package-info.java │ │ │ └── ws │ │ │ │ ├── CloseCode.java │ │ │ │ ├── OpCode.java │ │ │ │ ├── Speaking.java │ │ │ │ ├── WsEvent.java │ │ │ │ ├── in │ │ │ │ ├── ClientDisconnect.java │ │ │ │ ├── HeartbeatAck.java │ │ │ │ ├── Hello.java │ │ │ │ ├── Ignored.java │ │ │ │ ├── InboundWsEvent.java │ │ │ │ ├── Ready.java │ │ │ │ ├── Resumed.java │ │ │ │ ├── SessionDescription.java │ │ │ │ ├── Unknown.java │ │ │ │ ├── WebSocketClosed.java │ │ │ │ └── package-info.java │ │ │ │ ├── out │ │ │ │ ├── Heartbeat.java │ │ │ │ ├── Identify.java │ │ │ │ ├── OutboundWsEvent.java │ │ │ │ ├── Resume.java │ │ │ │ ├── SelectProtocol.java │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ └── package-info.java │ │ ├── immutables │ │ ├── ImmutableLcEvent.java │ │ ├── ImmutableWsEvent.java │ │ ├── SessionInfo.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── processing │ │ ├── PacketProvider.java │ │ ├── PacketUtil.java │ │ └── package-info.java │ └── test │ └── java │ └── space │ └── npstr │ └── magma │ └── impl │ └── EncryptionModeTest.java ├── jitpack.yml ├── platform ├── .gitignore └── gradle │ └── dependency-locks │ ├── archives.lockfile │ ├── classpath.lockfile │ ├── default.lockfile │ ├── jacocoAgent.lockfile │ └── jacocoAnt.lockfile ├── settings.gradle └── src └── main └── java └── space └── npstr └── magma └── MagmaFactory.java /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | /build/ 3 | /.gradle/ 4 | gradle.properties 5 | 6 | # eclipse files 7 | .classpath 8 | .project 9 | .settings/ 10 | /bin/ 11 | 12 | # intellij files 13 | /.idea/ 14 | out/* 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # netbeans files 20 | /.nb-gradle/ 21 | *.nb-gradle-properties 22 | 23 | # dolphin 24 | .directory 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | addons: 4 | sonarcloud: 5 | organization: "napstr-github" 6 | token: 7 | secure: "lWokv1JtxppNnKuf9U7UGBAMEAN9pK0hO+DNMujMHoU5B18+L/aTi5jBycTWji8dlAVkEMN87uDl0xooLc//wT1b42kI3R0j0oMJuUGUf/pTpqYUomEHs3rfUPCIKqqtUPln1qbMghJ0c6LOQzYTPOELKH66q1Hm+byvDSuFcljPKqqgQZRBtIN4u2ekJRIXqBoeDqoBf9MyxMWtNcDivmLVaLFIxuvPxaK+7aiKlul505MjfVb2uLy79fAfejfoz8VVX5lqF4pJraG8lhrFytMuDEa4ib4ICPXhDP1PapE7RPOLHdQq4HTrV//Xa7dTdTJh0g71I2NEiPTQEcW107LeEY2CVO3MVolrfulzl3vL79up5m5HI6TQCSfKM4LzdZ2mBIN87lg+3PO0v4FnXpjHWIlcz62nKfsjNHm1FeWIbhsqfHULh8Ugv688K0PmCnX0/2EYTVtNFo5ULSPNSFgPN3IB38HkjLAQp+uDMI4OEgInwDf/dutjd6AdjSb0e77ArQ28p5dHJCVSMAqOB/9s0SaLgaw4egy9mZpIgUIvlPca1Im85A1SHs6gmQk72TOnt2L/1NsCEyHIoU9jdU+nRghsaOUdHBSqvS7ZUXDIoRfrfZy2/PCCC3lIfT6vyhKdYtzsou4z8eebHXTQqk10mWHZMeowxfa5JcuwazE=" 8 | 9 | env: 10 | global: 11 | - BUILD_NUMBER: "$TRAVIS_BUILD_NUMBER" 12 | 13 | cache: 14 | directories: 15 | - "$HOME/.m2" 16 | - "$HOME/.gradle/wrapper" 17 | - "$HOME/.gradle/caches" 18 | - "$HOME/.sonar/cache" 19 | 20 | stages: 21 | - build & test 22 | - sonar 23 | 24 | jobs: 25 | fast_finish: true 26 | allow_failures: 27 | - jdk: openjdk-ea 28 | include: 29 | - stage: build & test 30 | jdk: openjdk8 31 | script: 32 | - "java -Xmx32m -version" 33 | - "./gradlew build --info" 34 | 35 | - stage: build & test 36 | jdk: openjdk11 37 | script: 38 | - "java -Xmx32m -version" 39 | - "./gradlew build --info" 40 | 41 | - stage: build & test 42 | jdk: openjdk12 43 | script: 44 | - "java -Xmx32m -version" 45 | - "./gradlew build --info" 46 | 47 | - stage: build & test 48 | jdk: openjdk-ea 49 | script: 50 | - "java -Xmx32m -version" 51 | - "./gradlew build --info" 52 | 53 | 54 | - stage: sonar 55 | jdk: openjdk11 56 | before_script: 57 | #for full sonar cloud blame information 58 | - "git fetch --unshallow" 59 | script: 60 | - "./gradlew sonarqube" 61 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | Expect breaking changes between minor versions while v1 has not been released. 4 | 5 | ### v0.12.6 6 | - Allow configuring SSL options 7 | 8 | ### v0.12.5 9 | - Add some more logging for errors in the websocket connection 10 | 11 | ### v0.12.4 12 | - Switch to "modern" echo message format [#25](https://github.com/napstr/Magma/pull/25) 13 | - Adhere to Discord's user agent format [#26](https://github.com/napstr/Magma/pull/26) 14 | - Build on jitpack with java8 [#27](https://github.com/napstr/Magma/pull/27) 15 | 16 | ### v0.12.3 17 | - Send a basic user agent header when connecting to Discord 18 | 19 | ### v0.12.2 20 | - Fixed jitpack versioning troubles. Previous versions might not resolve correctly, use this one instead. 21 | 22 | ### v0.12.0 23 | - Align versions with Spring Boot's dependency management (may result in versions of transitive dependencies going up / down) 24 | - Internal: Dependency locking & version ranges 25 | 26 | ### v0.11.0 27 | 28 | - Picked up the commits from [Minn's Magma fork](https://github.com/MinnDevelopment/Magma): 29 | 30 | - 0.10.4: Fix xsalsa20_poly1305 encryption (legacy support) 31 | - 0.10.3: Convert to single DatagramSocket shared by all audio pipelines 32 | - 0.10.2: Fix possible memory leak due to unclosed datagram sockets 33 | - 0.10.1: Fix incorrect handling of ByteBuffer in asDatagramPacket 34 | - 0.10.0: Restructure for JDA V4 compatibility 35 | - 0.9.2: Fix handling of 4014 close code 36 | - 0.9.1: 37 | - Add speaking modes 38 | - Change transitive dependencies to proper api/implementation scopes 39 | - Fix several javadoc errors 40 | - 0.9.0: Support Java 8 through 10 41 | 42 | 43 | ### ~~v0.10.0~~ 44 | 45 | This version has been skipped to avoid confusion with versions from [Minn's Magma fork](https://github.com/MinnDevelopment/Magma). 46 | Minn's improvements and updates have been incorporated in the next minor version. 47 | 48 | 49 | ### v0.9.0 50 | - Dependencies bumped 51 | - **Breaking**: Reorganized the project into `api` and `impl` modules, resulting in new Maven/Gradle coordinates and new packages of the Magma classes 52 |
Click me 53 | 54 | `space.npstr.magma.MagmaApi` -> `space.npstr.magma.api.MagmaApi` 55 | `space.npstr.magma.MdcKey` -> `space.npstr.magma.api.MdcKey` 56 | `space.npstr.magma.Member` -> `space.npstr.magma.api.Member` 57 | `space.npstr.magma.MagmaMember` -> `space.npstr.magma.api.MagmaMember` 58 | `space.npstr.magma.ServerUpdate` -> `space.npstr.magma.api.ServerUpdate` 59 | `space.npstr.magma.MagmaServerUpdate` -> `space.npstr.magma.api.MagmaServerUpdate` 60 | `space.npstr.magma.WebsocketConnectionState` -> `space.npstr.magma.api.WebsocketConnectionState` 61 | `space.npstr.magma.MagmaWebsocketConnectionState` -> `space.npstr.magma.api.MagmaWebsocketConnectionState` 62 | `space.npstr.magma.events.api.MagmaEvent` -> `space.npstr.magma.api.event.MagmaEvent` 63 | `space.npstr.magma.events.api.WebSocketClosed` -> `space.npstr.magma.api.event.WebSocketClosed` 64 | `space.npstr.magma.events.api.WebSocketClosedApiEvent` -> `space.npstr.magma.api.event.WebSocketClosedApiEvent` 65 | `MagmaApi.of` -> `MagmaFactory.of` 66 | 67 |
68 | 69 | ### v0.8.3 70 | - Fix bug with reconnecting in the same guild introduced in 0.8.2 71 | 72 | ### v0.8.2 73 | - Idempotent handling of connection requests [\#16](https://github.com/napstr/Magma/pull/16) (thanks @Frederikam) 74 | 75 | ### v0.8.1 76 | - Fix jitpack build 77 | 78 | ### v0.8.0 79 | - Add a WebsocketConnectionState to report the state of the websocket connections managed by a MagmaApi 80 | 81 | ### v0.7.0 82 | - Bump dependencies, including Java 11. 83 | 84 | ### v0.6.0 85 | - Introduce `MagmaApi#getEventStream` that allows user code to consume events from Magma, for example when the 86 | websocket is closed. 87 | 88 | ### v0.5.0 89 | - Port the remaining changes of JDA 3.7 (see [\#651](https://github.com/DV8FromTheWorld/JDA/pull/651)), 90 | notably the switch from `byte[]`s to `ByteBuffer`s in most places. This includes a backwards incompatible change to 91 | `IPacketProvider`, marking it as a non-threadsafe class. 92 | **Known issues:** Applications using [japp](https://github.com/Shredder121/jda-async-packetprovider) 93 | 1.2 or below have broken audio output. 94 | 95 | ### v0.4.5 96 | - Use direct byte buffers (off heap) for Undertow 97 | 98 | ### v0.4.4 99 | - Send our SSRC along with OP 5 Speaking updates 100 | - Add MDC and more trace logs to enable better reporting of issues 101 | - Update close code handling for expected 1xxx codes and warnings on suspicious closes 102 | - Deal with Opus interpolation 103 | 104 | ### v0.4.3 105 | - Fix 4003s closes due to sending events before identifying 106 | 107 | ### v0.4.0 108 | - Opus conversion removed. Send handlers are expected to provide opus packets. 109 | - Fix for a possible leak of send handlers 110 | 111 | ### v0.3.3 112 | - Fully event based AudioConnection 113 | - Correct Schedulers used for event processing 114 | - Dependency updates 115 | - Code quality improvements via SonarCloud 116 | 117 | ### v0.3.2 118 | - Log endpoint to which the connection has been closed along with the reason 119 | - Share a BufferPool between all connections to avoid memory leak 120 | 121 | ### v0.3.1 122 | - Dependency updates 123 | - Additional experimental build against Java 11 124 | 125 | ### v0.3.0 126 | - Type and parameter safety in the Api by introducing a simple DSL 127 | 128 | ### v0.2.1 129 | - Handle op 14 events 130 | 131 | ### v0.2.0 132 | - Build with java 10 133 | 134 | ### v0.1.2 135 | - Implement v4 of Discords Voice API 136 | 137 | ### v0.1.1 138 | - Depend on opus-java through jitpack instead of a git submodule 139 | 140 | ### v0.1.0 141 | - Ignore more irrelevant events 142 | - Smol docs update 143 | - Licensed as Apache 2.0 144 | - Use IP provided by Discord instead of endpoint address for the UDP connection 145 | 146 | ### v0.0.1 147 | - It's working, including resumes. 148 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /api/gradle/dependency-locks/annotationProcessor.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.immutables:value:2.8.3 5 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 6 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/archives.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/compile.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/compileClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.github.spotbugs:spotbugs-annotations:4.0.1 5 | com.google.code.findbugs:jsr305:3.0.2 6 | io.projectreactor:reactor-core:3.3.4.RELEASE 7 | org.immutables:value:2.8.3 8 | org.reactivestreams:reactive-streams:1.0.3 9 | org.slf4j:slf4j-api:1.7.30 10 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 11 | org.springframework:spring-beans:5.2.5.RELEASE 12 | org.springframework:spring-core:5.2.5.RELEASE 13 | org.springframework:spring-jcl:5.2.5.RELEASE 14 | org.springframework:spring-web:5.2.5.RELEASE 15 | org.springframework:spring-webflux:5.2.5.RELEASE 16 | space.npstr:annotations:0.0.2 17 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/compileOnly.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.github.spotbugs:spotbugs-annotations:4.0.1 5 | com.google.code.findbugs:jsr305:3.0.2 6 | org.immutables:value:2.8.3 7 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 8 | space.npstr:annotations:0.0.2 9 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/default.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | io.projectreactor:reactor-core:3.3.4.RELEASE 5 | org.reactivestreams:reactive-streams:1.0.3 6 | org.slf4j:slf4j-api:1.7.30 7 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 8 | org.springframework:spring-beans:5.2.5.RELEASE 9 | org.springframework:spring-core:5.2.5.RELEASE 10 | org.springframework:spring-jcl:5.2.5.RELEASE 11 | org.springframework:spring-web:5.2.5.RELEASE 12 | org.springframework:spring-webflux:5.2.5.RELEASE 13 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/jacocoAgent.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.jacoco:org.jacoco.agent:0.8.5 5 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/jacocoAnt.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.jacoco:org.jacoco.agent:0.8.5 5 | org.jacoco:org.jacoco.ant:0.8.5 6 | org.jacoco:org.jacoco.core:0.8.5 7 | org.jacoco:org.jacoco.report:0.8.5 8 | org.ow2.asm:asm-analysis:7.2 9 | org.ow2.asm:asm-commons:7.2 10 | org.ow2.asm:asm-tree:7.2 11 | org.ow2.asm:asm:7.2 12 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/runtime.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/runtimeClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | io.projectreactor:reactor-core:3.3.4.RELEASE 5 | org.reactivestreams:reactive-streams:1.0.3 6 | org.slf4j:slf4j-api:1.7.30 7 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 8 | org.springframework:spring-beans:5.2.5.RELEASE 9 | org.springframework:spring-core:5.2.5.RELEASE 10 | org.springframework:spring-jcl:5.2.5.RELEASE 11 | org.springframework:spring-web:5.2.5.RELEASE 12 | org.springframework:spring-webflux:5.2.5.RELEASE 13 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/testAnnotationProcessor.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/testCompile.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/testCompileClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | io.projectreactor:reactor-core:3.3.4.RELEASE 5 | org.apiguardian:apiguardian-api:1.1.0 6 | org.junit.jupiter:junit-jupiter-api:5.5.2 7 | org.junit.platform:junit-platform-commons:1.5.2 8 | org.opentest4j:opentest4j:1.2.0 9 | org.reactivestreams:reactive-streams:1.0.3 10 | org.slf4j:slf4j-api:1.7.30 11 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 12 | org.springframework:spring-beans:5.2.5.RELEASE 13 | org.springframework:spring-core:5.2.5.RELEASE 14 | org.springframework:spring-jcl:5.2.5.RELEASE 15 | org.springframework:spring-web:5.2.5.RELEASE 16 | org.springframework:spring-webflux:5.2.5.RELEASE 17 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/testCompileOnly.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/testRuntime.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /api/gradle/dependency-locks/testRuntimeClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | ch.qos.logback:logback-classic:1.2.3 5 | ch.qos.logback:logback-core:1.2.3 6 | io.projectreactor:reactor-core:3.3.4.RELEASE 7 | org.apiguardian:apiguardian-api:1.1.0 8 | org.junit.jupiter:junit-jupiter-api:5.5.2 9 | org.junit.jupiter:junit-jupiter-engine:5.5.2 10 | org.junit.platform:junit-platform-commons:1.5.2 11 | org.junit.platform:junit-platform-engine:1.5.2 12 | org.opentest4j:opentest4j:1.2.0 13 | org.reactivestreams:reactive-streams:1.0.3 14 | org.slf4j:slf4j-api:1.7.30 15 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 16 | org.springframework:spring-beans:5.2.5.RELEASE 17 | org.springframework:spring-core:5.2.5.RELEASE 18 | org.springframework:spring-jcl:5.2.5.RELEASE 19 | org.springframework:spring-web:5.2.5.RELEASE 20 | org.springframework:spring-webflux:5.2.5.RELEASE 21 | -------------------------------------------------------------------------------- /api/src/main/java/net/dv8tion/jda/api/audio/AudioSendHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2017 Austin Keener & Michael Ritter 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.dv8tion.jda.api.audio; 18 | 19 | import javax.sound.sampled.AudioFormat; 20 | import java.nio.ByteBuffer; 21 | 22 | /** 23 | * Interface used to send audio to Discord through JDA. 24 | */ 25 | public interface AudioSendHandler 26 | { 27 | /** 28 | * Audio Input Format expected by JDA if {@link #isOpus()} returns false. 48KHz 16bit stereo signed BigEndian PCM. 29 | */ 30 | AudioFormat INPUT_FORMAT = new AudioFormat(48000f, 16, 2, true, true); 31 | 32 | /** 33 | * If this method returns true JDA will attempt to retrieve audio data from this handler by calling 34 | * {@link #provide20MsAudio()}. The return value is checked each time JDA attempts send audio, so if 35 | * the developer wanted to start and stop sending audio it could be done by changing the value returned 36 | * by this method at runtime. 37 | * 38 | * @return If true, JDA will attempt to retrieve audio data from {@link #provide20MsAudio()} 39 | */ 40 | boolean canProvide(); 41 | 42 | /** 43 | * If {@link #canProvide()} returns true JDA will call this method in an attempt to retrieve audio data from the 44 | * handler. This method need to provide 20 Milliseconds of audio data as a byte array. 45 | *

46 | * Considering this system needs to be low-latency / high-speed, it is recommended that the loading of audio data 47 | * be done before hand or in parallel and not loaded from disk when this method is called by JDA. Attempting to load 48 | * all audio data from disk when this method is called will most likely cause issues due to IO blocking this thread. 49 | *

50 | * The provided audio data needs to be in the format: 48KHz 16bit stereo signed BigEndian PCM. 51 | *
Defined by: {@link net.dv8tion.jda.api.audio.AudioSendHandler#INPUT_FORMAT AudioSendHandler.INPUT_FORMAT}. 52 | *
If {@link #isOpus()} is set to return true, then it should be in pre-encoded Opus format instead. 53 | * 54 | * @return Should return a byte[] containing 20 Milliseconds of audio. 55 | */ 56 | ByteBuffer provide20MsAudio(); 57 | 58 | /** 59 | * If this method returns true JDA will treat the audio data provided by {@link #provide20MsAudio()} as a pre-encoded 60 | * 20 Millisecond packet of Opus audio. This means that JDA WILL NOT attempt to encode the audio as Opus, but 61 | * will provide it to Discord exactly as it is given. 62 | * 63 | * @return If true, JDA will not attempt to encode the provided audio data as Opus. 64 | *
Default - False. 65 | */ 66 | default boolean isOpus() 67 | { 68 | return false; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /api/src/main/java/net/dv8tion/jda/api/audio/factory/IAudioSendFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2018 Austin Keener & Michael Ritter & Florian Spieß 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.dv8tion.jda.api.audio.factory; 18 | 19 | /** 20 | * Factory interface for the creation of new {@link net.dv8tion.jda.api.audio.factory.IAudioSendSystem IAudioSendSystem} objects. 21 | */ 22 | public interface IAudioSendFactory 23 | { 24 | /** 25 | * Called by JDA's audio system when a new {@link net.dv8tion.jda.api.audio.factory.IAudioSendSystem IAudioSendSystem} 26 | * instance is needed to handle the sending of UDP audio packets to discord. 27 | * 28 | * @param packetProvider 29 | * The connection provided to the new {@link net.dv8tion.jda.api.audio.factory.IAudioSendSystem IAudioSendSystem} 30 | * object for proper setup and usage. 31 | * 32 | * @return The newly constructed IAudioSendSystem, ready for {@link IAudioSendSystem#start()} to be called. 33 | */ 34 | IAudioSendSystem createSendSystem(IPacketProvider packetProvider); 35 | } 36 | -------------------------------------------------------------------------------- /api/src/main/java/net/dv8tion/jda/api/audio/factory/IAudioSendSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2018 Austin Keener & Michael Ritter & Florian Spieß 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.dv8tion.jda.api.audio.factory; 18 | 19 | /** 20 | * Interface that acts as a UDP audio packet sending loop. 21 | *
This interface is provided so that developers can provide their own implementation for different management 22 | * of thread pools, process usage, and even for forwarding to native binaries implemented in other languages like C 23 | * to avoid problems with JVM GC StopTheWorld events. 24 | */ 25 | public interface IAudioSendSystem 26 | { 27 | /** 28 | * This represents the start of the loop, similar to {@link Thread#start()}, and after a call to this method JDA 29 | * assumes that the instance will be sending UDP audio packets in a loop. 30 | *

31 | * Note: The packet sending loop should NOT be started on the current thread. I.E: This method should not 32 | * block forever, in the same way that {@link Thread#start()} does not. Just like in Thread, the running action of 33 | * this system should be implemented asynchronously. 34 | */ 35 | void start(); 36 | 37 | /** 38 | * This represents the destruction of this instance and should be used to perform all necessary cleanup and shutdown 39 | * operations needed to free resources. 40 | *

41 | * Note: This method can be called at any time after instance creation ({@link #start()} may not yet have been called), 42 | * and it is possible that this method could be called more than once due to edge-case shutdown conditions. 43 | */ 44 | void shutdown(); 45 | } 46 | -------------------------------------------------------------------------------- /api/src/main/java/net/dv8tion/jda/api/audio/factory/IPacketProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2018 Austin Keener & Michael Ritter & Florian Spieß 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.dv8tion.jda.api.audio.factory; 18 | 19 | import net.dv8tion.jda.api.audio.hooks.ConnectionStatus; 20 | 21 | import javax.annotation.concurrent.NotThreadSafe; 22 | import java.net.DatagramPacket; 23 | import java.net.DatagramSocket; 24 | import java.net.InetSocketAddress; 25 | import java.nio.ByteBuffer; 26 | 27 | /** 28 | * Represents the connection between a {@link net.dv8tion.jda.api.audio.factory.IAudioSendSystem IAudioSendSystem} and 29 | * JDA's internal audio system, providing access to audio packets built from data provided from 30 | * {@link net.dv8tion.jda.api.audio.AudioSendHandler AudioSendHandlers}. 31 | * 32 | *

Note that this provider is not thread-safe! 33 | */ 34 | @NotThreadSafe 35 | public interface IPacketProvider 36 | { 37 | /** 38 | * Provides a unique String identifier for the connection. 39 | *
Uses shard information and specific audio connection information to build string. 40 | * 41 | *

THIS IS NOT AVAILABLE IN MAGMA 42 | * 43 | * @return Never-null String unique to this audio connection. 44 | */ 45 | String getIdentifier(); 46 | 47 | /** 48 | * Provides the current channel that this connection is transmitting to. 49 | * 50 | *

THIS IS NOT AVAILABLE IN MAGMA 51 | * 52 | * @return The id of the connected channel that this connection is sending to. 53 | */ 54 | String getConnectedChannel(); 55 | 56 | /** 57 | * The UDP connection for this audio connection. The Send System 58 | * uses this socket to send audio packets to discord, and this is also the socket used to receive audio packets from discord. 59 | *
If you are implementing your own system, it is recommended that you used this connection as it is part of JDA's internal 60 | * system that JDA monitors for errors and closures. It should be noted however that using this is not required to 61 | * send audio packets if the developer wishes to open their own UDP socket to send from. 62 | * 63 | * @return The UDP socket connection used for audio sending. 64 | */ 65 | DatagramSocket getUdpSocket(); 66 | 67 | /** 68 | * The connected socket address for this audio connection. This can be useful for developers 69 | * to open their own socket for datagram sending and allows to avoid using {@link #getNextPacket(boolean)}. 70 | * 71 | * @return {@link InetSocketAddress} of the current UDP connection 72 | */ 73 | InetSocketAddress getSocketAddress(); 74 | 75 | /** 76 | * Used to retrieve an audio packet to send to Discord. The packet provided is already converted to Opus and 77 | * encrypted, and as such is completely ready to be sent to Discord. The {@code changeTalking} parameter is used 78 | * to control whether or not the talking indicator should be changed if the 79 | * {@link net.dv8tion.jda.api.audio.AudioSendHandler AudioSendHandler} cannot provide an audio packet. 80 | * 81 | *

Use case for this parameter would be front-loading or queuing many audio packets ahead of send time, and if the AudioSendHandler 82 | * did not have enough to fill the entire queue, you would have {@code changeTalking} set to {@code false} until the queue 83 | * was empty. At that point, you would switch to {@code true} when requesting a new packet due to the fact that if 84 | * one was not available, the developer would not have a packet to send, thus the logged in account is no longer "talking". 85 | * 86 | *

Note: When the AudioSendHandler cannot or does not provide a new packet to send, this method will return null. 87 | * 88 | *

The buffer used here may be used again on the next call to this getter, if you plan on storing the data copy it. 89 | * The buffer was created using {@link ByteBuffer#allocate(int)} and is not direct. 90 | * 91 | * @param changeTalking 92 | * Whether or not to change the talking indicator if the AudioSendHandler cannot provide a new audio packet. 93 | * 94 | * @return Possibly-null {@link ByteBuffer} containing an encoded and encrypted packet 95 | * of audio data ready to be sent to discord. 96 | */ 97 | ByteBuffer getNextPacketRaw(boolean changeTalking); 98 | 99 | /** 100 | * Used to retrieve an audio packet to send to Discord. The packet provided is already converted to Opus and 101 | * encrypted, and as such is completely ready to be sent to Discord. The {@code changeTalking} parameter is used 102 | * to control whether or not the talking indicator should be changed if the 103 | * {@link net.dv8tion.jda.api.audio.AudioSendHandler AudioSendHandler} cannot provide an audio packet. 104 | * 105 | *

Use case for this parameter would be front-loading or queuing many audio packets ahead of send time, and if the AudioSendHandler 106 | * did not have enough to fill the entire queue, you would have {@code changeTalking} set to {@code false} until the queue 107 | * was empty. At that point, you would switch to {@code true} when requesting a new packet due to the fact that if 108 | * one was not available, the developer would not have a packet to send, thus the logged in account is no longer "talking". 109 | * 110 | *

Note: When the AudioSendHandler cannot or does not provide a new packet to send, this method will return null. 111 | * 112 | * @param changeTalking 113 | * Whether or not to change the talking indicator if the AudioSendHandler cannot provide a new audio packet. 114 | * 115 | * @return Possibly-null {@link java.net.DatagramPacket DatagramPacket} containing an encoded and encrypted packet 116 | * of audio data ready to be sent to discord. 117 | */ 118 | DatagramPacket getNextPacket(boolean changeTalking); 119 | 120 | /** 121 | * This method is used to indicate a connection error to JDA so that the connection can be properly shutdown. 122 | *
This is useful if, during setup or operation, an unrecoverable error is encountered. 123 | * 124 | * @param status 125 | * The {@link net.dv8tion.jda.api.audio.hooks.ConnectionStatus ConnectionStatus} being reported to JDA 126 | * indicating an error with connection. 127 | */ 128 | void onConnectionError(ConnectionStatus status); 129 | 130 | /** 131 | * This method is used to indicate to JDA that the UDP connection has been lost, whether that be due internet loss 132 | * or some other unknown reason. This is similar to 133 | * {@link #onConnectionError(net.dv8tion.jda.api.audio.hooks.ConnectionStatus)} as it provides a default error 134 | * reason of {@link net.dv8tion.jda.api.audio.hooks.ConnectionStatus#ERROR_LOST_CONNECTION}. 135 | */ 136 | void onConnectionLost(); 137 | } 138 | -------------------------------------------------------------------------------- /api/src/main/java/net/dv8tion/jda/api/audio/hooks/ConnectionStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2017 Austin Keener & Michael Ritter 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.dv8tion.jda.api.audio.hooks; 18 | 19 | /** 20 | * Represents the connection status of an audio connection. 21 | * 22 | * NOTE: This class is only present in Magma due to {@link net.dv8tion.jda.api.audio.factory.IPacketProvider IPacketProviders} 23 | * dependency on it 24 | */ 25 | public enum ConnectionStatus 26 | { 27 | /** Indicates that there is no open connection or that the connection was closed by choice, not by error.*/ 28 | NOT_CONNECTED, 29 | /** JDA is waiting on Discord to send a valid endpoint which to connect the audio websocket to.*/ 30 | CONNECTING_AWAITING_ENDPOINT, 31 | /** JDA has received a valid endpoint and is attempting to setup and connect the audio websocket */ 32 | CONNECTING_AWAITING_WEBSOCKET_CONNECT, 33 | /** JDA has connected the audio websocket to Discord and has sent the authentication information, awaiting reply.*/ 34 | CONNECTING_AWAITING_AUTHENTICATING, 35 | /** 36 | * JDA successfully authenticated the audio websocket and it now attempting UDP discovery. UDP discovery involves 37 | * opening a UDP socket and sending a packet to a provided Discord remote resource which responds with the 38 | * external ip and port which the packet was sent from. 39 | */ 40 | CONNECTING_ATTEMPTING_UDP_DISCOVERY, 41 | /** 42 | * After determining our external ip and port, JDA forwards this information to Discord so that it can send 43 | * audio packets for us to properly receive. At this point, JDA is waiting for final websocket READY. 44 | */ 45 | CONNECTING_AWAITING_READY, 46 | /** The audio connection has been successfully setup and is ready for use. */ 47 | CONNECTED, 48 | /** 49 | * Indicates that the channel which the audio connection was connected to was deleted, thus the connection was severed. 50 | *
This is a non-reconnectable error. 51 | * */ 52 | DISCONNECTED_CHANNEL_DELETED, 53 | 54 | //All will attempt to reconnect unless autoReconnect is disabled 55 | /** 56 | * Indicates that the connection was lost, either via UDP socket problems or the audio Websocket disconnecting. 57 | *
This is typically caused by a brief loss of internet which results in connection loss. 58 | *
JDA automatically attempts to reconnect when this error occurs. 59 | */ 60 | ERROR_LOST_CONNECTION, 61 | /** 62 | * Indicates that the audio Websocket was unable to connect to discord. This could be due to an internet 63 | * problem causing a connection problem or an error on Discord's side (possibly due to load) 64 | *
JDA automatically attempts to reconnect when this error occurs. 65 | */ 66 | ERROR_WEBSOCKET_UNABLE_TO_CONNECT, 67 | /** 68 | * Indicates that the UDP setup failed. This is caused when JDA cannot properly communicate with Discord to 69 | * discover the system's external IP and port which audio data will be sent from. Typically caused by an internet 70 | * problem or an overly aggressive NAT port table. 71 | *
JDA automatically attempts to reconnect when this error occurs. 72 | */ 73 | ERROR_UDP_UNABLE_TO_CONNECT, 74 | ERROR_CANNOT_RESUME, DISCONNECTED_AUTHENTICATION_FAILURE, 75 | } 76 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/MagmaApi.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api; 18 | 19 | import edu.umd.cs.findbugs.annotations.Nullable; 20 | import net.dv8tion.jda.api.audio.AudioSendHandler; 21 | import reactor.core.publisher.Flux; 22 | import space.npstr.magma.api.event.MagmaEvent; 23 | 24 | import java.net.DatagramSocket; 25 | import java.util.List; 26 | import java.util.Set; 27 | 28 | /** 29 | * Created by napster on 24.04.18. 30 | *

31 | * Public API. These methods may be called by users of Magma. 32 | */ 33 | @SuppressWarnings("unused") 34 | public interface MagmaApi { 35 | 36 | /** 37 | * The UDP client used to NAT hole punch. 38 | *
This is closed by {@link #shutdown()}. 39 | * 40 | * @return The DatagramSocket 41 | */ 42 | DatagramSocket getDatagramSocket(); 43 | 44 | /** 45 | * Release all resources held. 46 | */ 47 | void shutdown(); 48 | 49 | /** 50 | * @return a Reactor stream that can be subscribed to for event handling 51 | */ 52 | Flux getEventStream(); 53 | 54 | /** 55 | * Also see: https://discordapp.com/developers/docs/topics/voice-connections#retrieving-voice-server-information-example-voice-server-update-payload 56 | * 57 | * @param member 58 | * Id of the bot account that this update belongs to composed with the id of the guild whose voice server 59 | * shall be updated. The user id is something your use code should keep track of, the guild id can be 60 | * extracted from the op 0 VOICE_SERVER_UPDATE event that should be triggering a call to this method in the 61 | * first place. 62 | * @param serverUpdate 63 | * A composite of session id, endpoint and token. Most of that information can be extracted from the op 0 64 | * VOICE_SERVER_UPDATE event that should be triggering a call to this method in the first place. 65 | * 66 | * @see Member 67 | * @see ServerUpdate 68 | */ 69 | void provideVoiceServerUpdate(final Member member, final ServerUpdate serverUpdate); 70 | 71 | /** 72 | * Set the {@link AudioSendHandler} for a bot member. 73 | * 74 | * @param member 75 | * user id + guild id of the bot member for which the send handler shall be set 76 | * @param sendHandler 77 | * The send handler to be set. You need to implement this yourself. This is a JDA interface so if you have 78 | * written voice code with JDA before you reuse your existing code. 79 | * 80 | * @see Member 81 | */ 82 | void setSendHandler(final Member member, final AudioSendHandler sendHandler); 83 | 84 | /** 85 | * The {@link SpeakingMode SpeakingMode} to use. 86 | * @param member 87 | * user id + guild id of the bot member for which the speaking mode shall be set 88 | * @param mode 89 | * EnumSet containing the speaking modes to apply 90 | * @see Member 91 | */ 92 | void setSpeakingMode(final Member member, @Nullable final Set mode); 93 | 94 | /** 95 | * Remove the {@link AudioSendHandler} for a bot member. 96 | * 97 | * @param member 98 | * user id + guild id of the bot member for which the send handler shall be removed 99 | * 100 | * @see Member 101 | */ 102 | void removeSendHandler(final Member member); 103 | 104 | /** 105 | * Close the audio connection for a bot member. 106 | * 107 | * @param member 108 | * user id + guild id of the bot member for which the audio connection shall be closed 109 | * 110 | * @see Member 111 | */ 112 | void closeConnection(final Member member); 113 | 114 | 115 | /** 116 | * @return a list of all {@link WebsocketConnectionState WebsocketConnectionStates} detailing the state of 117 | * the audio stacks managed by this {@link MagmaApi} instance 118 | */ 119 | List getAudioConnectionStates(); 120 | } 121 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/MdcKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api; 18 | 19 | /** 20 | * Created by napster on 17.07.18. 21 | */ 22 | public class MdcKey { 23 | 24 | public static final String GUILD = "mmGuild"; 25 | public static final String BOT = "mmBot"; 26 | 27 | private MdcKey() {} 28 | } 29 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/Member.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api; 18 | 19 | import org.immutables.value.Value; 20 | 21 | /** 22 | * Created by napster on 23.05.18. 23 | *

24 | * Discord supports one single audio connection per user and guild (also called a "member"). 25 | * This means, an audio connection is exactly identified by those two datapoints, 26 | * and all of the methods of Magma require those to correctly identify the connection 27 | * that you want to open/close/change something about. 28 | *

29 | * This is the class that composes the user id and guild id to identify such a member. 30 | *

31 | * Build one of these by using the autogenerated builder like so: 32 | *

33 |  * {@code
34 |  * Member someMember = MagmaMember.builder()
35 |  *      .userId("...")
36 |  *      .guildId("...")
37 |  *      .build();
38 |  * }
39 |  * 
40 | */ 41 | @Value.Immutable 42 | @Value.Style( 43 | typeAbstract = "*", 44 | typeImmutable = "Magma*" 45 | ) 46 | public abstract class Member { 47 | 48 | /** 49 | * Discord snowflake detailing the user of this member 50 | */ 51 | public abstract String getUserId(); 52 | 53 | /** 54 | * Discord snowflake detailing the guild of this member 55 | */ 56 | public abstract String getGuildId(); 57 | 58 | 59 | @Value.Check 60 | protected void stringsNotEmpty() { 61 | final String userId = this.getUserId(); 62 | if (userId.isEmpty()) { 63 | throw new IllegalArgumentException("Provided user id is empty!"); 64 | } 65 | try { 66 | //noinspection ResultOfMethodCallIgnored 67 | Long.parseUnsignedLong(userId); 68 | } catch (final NumberFormatException e) { 69 | throw new IllegalArgumentException("Provided user id '" + userId + "' is not a valid discord snowflake."); 70 | } 71 | 72 | 73 | final String guildId = this.getGuildId(); 74 | if (guildId.isEmpty()) { 75 | throw new IllegalArgumentException("Provided guild id is empty!"); 76 | } 77 | 78 | try { 79 | //noinspection ResultOfMethodCallIgnored 80 | Long.parseUnsignedLong(guildId); 81 | } catch (final NumberFormatException e) { 82 | throw new IllegalArgumentException("Provided guild id '" + guildId + "' is not a valid discord snowflake."); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/ServerUpdate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api; 18 | 19 | import org.immutables.value.Value; 20 | 21 | /** 22 | * Created by napster on 23.05.18. 23 | *

24 | * Build one of these by using the autogenerated builder like so: 25 | *

26 |  * {@code
27 |  * ServerUpdate serverUpdate = MagmaServerUpdate.builder()
28 |  *      .sessionId("...")
29 |  *      .endpoint("...")
30 |  *      .token("...")
31 |  *      .build();
32 |  * }
33 |  * 
34 | */ 35 | @Value.Immutable 36 | @Value.Style( 37 | typeAbstract = "*", 38 | typeImmutable = "Magma*" 39 | ) 40 | public abstract class ServerUpdate { 41 | 42 | /** 43 | * The session id of the voice state of the member to which this server update belongs. 44 | */ 45 | public abstract String getSessionId(); 46 | 47 | /** 48 | * The endpoint to connect to. If the event you received from Discord has no endpoint, you can safely 49 | * discard it, until you received one with a valid endpoint. Can be extracted from the op 0 50 | * VOICE_SERVER_UPDATE event. 51 | */ 52 | public abstract String getEndpoint(); 53 | 54 | /** 55 | * Token that can be extracted from the op 0 VOICE_SERVER_UPDATE event. 56 | */ 57 | public abstract String getToken(); 58 | 59 | 60 | @Value.Check 61 | protected void stringsNotEmpty() { 62 | if (this.getSessionId().isEmpty()) { 63 | throw new IllegalArgumentException("Provided session id is empty!"); 64 | } 65 | if (this.getEndpoint().isEmpty()) { 66 | throw new IllegalArgumentException("Provided endpoint is empty!"); 67 | } 68 | if (this.getToken().isEmpty()) { 69 | throw new IllegalArgumentException("Provided token is empty!"); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/SpeakingMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Florian Spieß 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api; 18 | 19 | import edu.umd.cs.findbugs.annotations.Nullable; 20 | 21 | import java.util.Set; 22 | 23 | public enum SpeakingMode { 24 | VOICE(1), 25 | SOUNDSHARE(2), 26 | PRIORITY(4); 27 | 28 | private final int key; 29 | 30 | SpeakingMode(int key) { 31 | this.key = key; 32 | } 33 | 34 | public int getKey() { 35 | return key; 36 | } 37 | 38 | public static int toMask(@Nullable Set mode) { 39 | if (mode == null || mode.isEmpty()) { 40 | return 0; 41 | } 42 | int mask = 0; 43 | for (SpeakingMode m : mode) { 44 | mask |= m.getKey(); 45 | } 46 | return mask; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/WebsocketConnectionState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api; 18 | 19 | import org.immutables.value.Value; 20 | 21 | /** 22 | * Created by napster on 25.10.18. 23 | */ 24 | @Value.Immutable 25 | @Value.Style( 26 | typeAbstract = "*", 27 | typeImmutable = "Magma*" 28 | ) 29 | public abstract class WebsocketConnectionState { 30 | 31 | /** 32 | * @return user and guild coordinates of this audio connection 33 | */ 34 | public abstract Member getMember(); 35 | 36 | /** 37 | * @return phase the websocket connection of this member finds itself in, see {@link Phase} 38 | */ 39 | public abstract Phase getPhase(); 40 | 41 | 42 | public enum Phase { 43 | 44 | /** 45 | * The connection is connecting initially. 46 | */ 47 | CONNECTING, 48 | 49 | /** 50 | * We have received a Ready or Resume payload and have not disconnected yet. 51 | */ 52 | CONNECTED, 53 | 54 | /** 55 | * Websocket connection has been closed, either by Discord, due to internet issues, or by ourselves. 56 | */ 57 | DISCONNECTED, 58 | 59 | /** 60 | * We are attempting to resume the connection. 61 | */ 62 | RESUMING, 63 | 64 | /** 65 | * No websocket connection is present, even though there is an audio stack present for this member. 66 | */ 67 | NO_CONNECTION, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/event/ImmutableApiEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api.event; 18 | 19 | import org.immutables.value.Value; 20 | 21 | @Value.Style( 22 | typeImmutable = "*ApiEvent", 23 | stagedBuilder = true 24 | ) 25 | public @interface ImmutableApiEvent {} 26 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/event/MagmaEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api.event; 18 | 19 | /** 20 | * Base interface of API events 21 | */ 22 | public interface MagmaEvent {} 23 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/event/WebSocketClosed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api.event; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.api.Member; 21 | 22 | /** 23 | * This event is fired when an audio web socket is closed. However, this event is not fired if we are trying to 24 | * resume (i.e reconnect) automatically unless resuming causes a new socket to close. 25 | */ 26 | @SuppressWarnings("unused") 27 | @Value.Immutable 28 | @ImmutableApiEvent 29 | public abstract class WebSocketClosed implements MagmaEvent { 30 | 31 | public abstract Member getMember(); 32 | 33 | public abstract int getCloseCode(); 34 | 35 | public abstract String getReason(); 36 | 37 | public abstract boolean isByRemote(); 38 | } 39 | -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/event/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.api.event; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /api/src/main/java/space/npstr/magma/api/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.api; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /api/src/test/java/space/npstr/magma/api/MemberTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api; 18 | 19 | import org.junit.jupiter.api.Test; 20 | import org.junit.jupiter.api.function.Executable; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertEquals; 23 | import static org.junit.jupiter.api.Assertions.assertThrows; 24 | 25 | /** 26 | * Created by napster on 23.05.18. 27 | */ 28 | public class MemberTest { 29 | 30 | @Test 31 | public void emptyUserId() { 32 | final Executable ex = () -> MagmaMember.builder() 33 | .userId("") 34 | .guildId("174820236481134592") 35 | .build(); 36 | 37 | assertThrows(IllegalArgumentException.class, ex, "Accepted empty user id"); 38 | } 39 | 40 | @Test 41 | public void emptyGuildId() { 42 | final Executable ex = () -> MagmaMember.builder() 43 | .userId("166604053629894657") 44 | .guildId("") 45 | .build(); 46 | 47 | assertThrows(IllegalArgumentException.class, ex, "Accepted empty guild id"); 48 | } 49 | 50 | 51 | @Test 52 | public void userIdNotASnowflake() { 53 | final Executable ex = () -> MagmaMember.builder() 54 | .userId("this is not a valid snowflake") 55 | .guildId("174820236481134592") 56 | .build(); 57 | 58 | assertThrows(IllegalArgumentException.class, ex, "Accepted obviously invalid snowflake as a user id"); 59 | } 60 | 61 | 62 | @Test 63 | public void guildIdNotASnowflake() { 64 | final Executable ex = () -> MagmaMember.builder() 65 | .userId("166604053629894657") 66 | .guildId("totally valid snowflake kappa") 67 | .build(); 68 | 69 | assertThrows(IllegalArgumentException.class, ex, "Accepted obviously invalid snowflake as a guild id"); 70 | } 71 | 72 | @Test 73 | public void valid() { 74 | final String userId = "166604053629894657"; 75 | final String guildId = "174820236481134592"; 76 | final Member member = MagmaMember.builder() 77 | .userId(userId) 78 | .guildId(guildId) 79 | .build(); 80 | 81 | assertEquals(userId, member.getUserId(), "User id modified by builder"); 82 | assertEquals(guildId, member.getGuildId(), "Guild id modified by builder"); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /api/src/test/java/space/npstr/magma/api/ServerUpdateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.api; 18 | 19 | import org.junit.jupiter.api.Test; 20 | import org.junit.jupiter.api.function.Executable; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertEquals; 23 | import static org.junit.jupiter.api.Assertions.assertThrows; 24 | 25 | /** 26 | * Created by napster on 23.05.18. 27 | */ 28 | public class ServerUpdateTest { 29 | 30 | @Test 31 | public void emptySessionId() { 32 | final Executable ex = () -> MagmaServerUpdate.builder() 33 | .sessionId("") 34 | .endpoint("127.0.0.1") 35 | .token("top_secret") 36 | .build(); 37 | 38 | assertThrows(IllegalArgumentException.class, ex, "Accepted empty session id"); 39 | } 40 | 41 | @Test 42 | public void emptyEndpoint() { 43 | final Executable ex = () -> MagmaServerUpdate.builder() 44 | .sessionId("blargh") 45 | .endpoint("") 46 | .token("top_secret") 47 | .build(); 48 | 49 | assertThrows(IllegalArgumentException.class, ex, "Accepted empty endpoint"); 50 | } 51 | 52 | @Test 53 | public void emptyToken() { 54 | final Executable ex = () -> MagmaServerUpdate.builder() 55 | .sessionId("blargh") 56 | .endpoint("127.0.0.1") 57 | .token("") 58 | .build(); 59 | 60 | assertThrows(IllegalArgumentException.class, ex, "Accepted empty token"); 61 | } 62 | 63 | @Test 64 | public void valid() { 65 | final String sessionId = "blargh"; 66 | final String endpoint = "127.0.0.1"; 67 | final String token = "top_secret"; 68 | final ServerUpdate serverUpdate = MagmaServerUpdate.builder() 69 | .sessionId(sessionId) 70 | .endpoint(endpoint) 71 | .token(token) 72 | .build(); 73 | 74 | assertEquals(sessionId, serverUpdate.getSessionId(), "Session id modified by builder"); 75 | assertEquals(endpoint, serverUpdate.getEndpoint(), "Endpoint modified by builder"); 76 | assertEquals(token, serverUpdate.getToken(), "Token modified by builder"); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /gradle/dependency-locks/annotationProcessor.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.immutables:value:2.8.3 5 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 6 | -------------------------------------------------------------------------------- /gradle/dependency-locks/archives.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /gradle/dependency-locks/buildscript-classpath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | backport-util-concurrent:backport-util-concurrent:3.1 5 | classworlds:classworlds:1.1-alpha-2 6 | com.github.ben-manes:gradle-versions-plugin:0.28.0 7 | com.googlecode.javaewah:JavaEWAH:1.1.6 8 | com.jcraft:jsch:0.1.55 9 | com.jcraft:jzlib:1.1.1 10 | com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4 11 | com.thoughtworks.xstream:xstream:1.4.10 12 | commons-beanutils:commons-beanutils:1.8.0 13 | commons-codec:commons-codec:1.6 14 | commons-collections:commons-collections:3.2.1 15 | commons-lang:commons-lang:2.4 16 | commons-logging:commons-logging:1.1.1 17 | junit:junit:3.8.1 18 | nekohtml:nekohtml:1.9.6.2 19 | nekohtml:xercesMinimal:1.9.6.2 20 | net.sf.ezmorph:ezmorph:1.0.6 21 | net.sf.json-lib:json-lib:2.3 22 | net.sourceforge.nekohtml:nekohtml:1.9.16 23 | org.ajoberstar.grgit:grgit-core:4.0.1 24 | org.ajoberstar.grgit:grgit-gradle:4.0.1 25 | org.apache.ant:ant-launcher:1.8.0 26 | org.apache.ant:ant:1.8.0 27 | org.apache.httpcomponents:httpclient:4.2.1 28 | org.apache.httpcomponents:httpcore:4.2.1 29 | org.apache.maven.wagon:wagon-file:1.0-beta-6 30 | org.apache.maven.wagon:wagon-http-lightweight:1.0-beta-6 31 | org.apache.maven.wagon:wagon-http-shared:1.0-beta-6 32 | org.apache.maven.wagon:wagon-provider-api:1.0-beta-6 33 | org.apache.maven:maven-ant-tasks:2.1.3 34 | org.apache.maven:maven-artifact-manager:2.2.1 35 | org.apache.maven:maven-artifact:2.2.1 36 | org.apache.maven:maven-error-diagnostics:2.2.1 37 | org.apache.maven:maven-model:2.2.1 38 | org.apache.maven:maven-plugin-registry:2.2.1 39 | org.apache.maven:maven-profile:2.2.1 40 | org.apache.maven:maven-project:2.2.1 41 | org.apache.maven:maven-repository-metadata:2.2.1 42 | org.apache.maven:maven-settings:2.2.1 43 | org.bouncycastle:bcpg-jdk15on:1.64 44 | org.bouncycastle:bcpkix-jdk15on:1.64 45 | org.bouncycastle:bcprov-jdk15on:1.64 46 | org.codehaus.groovy.modules.http-builder:http-builder:0.7.2 47 | org.codehaus.plexus:plexus-container-default:1.0-alpha-9-stable-1 48 | org.codehaus.plexus:plexus-interpolation:1.11 49 | org.codehaus.plexus:plexus-utils:1.5.15 50 | org.eclipse.jgit:org.eclipse.jgit:5.6.0.201912101111-r 51 | org.slf4j:slf4j-api:1.7.2 52 | org.sonarsource.scanner.api:sonar-scanner-api:2.14.0.2002 53 | org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969 54 | xerces:xercesImpl:2.9.1 55 | xml-resolver:xml-resolver:1.2 56 | xmlpull:xmlpull:1.1.3.1 57 | xpp3:xpp3_min:1.1.4c 58 | -------------------------------------------------------------------------------- /gradle/dependency-locks/compile.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /gradle/dependency-locks/compileClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.github.spotbugs:spotbugs-annotations:4.0.1 5 | com.google.code.findbugs:jsr305:3.0.2 6 | io.projectreactor:reactor-core:3.3.4.RELEASE 7 | org.immutables:value:2.8.3 8 | org.reactivestreams:reactive-streams:1.0.3 9 | org.slf4j:slf4j-api:1.7.30 10 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 11 | org.springframework:spring-beans:5.2.5.RELEASE 12 | org.springframework:spring-core:5.2.5.RELEASE 13 | org.springframework:spring-jcl:5.2.5.RELEASE 14 | org.springframework:spring-web:5.2.5.RELEASE 15 | org.springframework:spring-webflux:5.2.5.RELEASE 16 | space.npstr:annotations:0.0.2 17 | -------------------------------------------------------------------------------- /gradle/dependency-locks/compileOnly.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.github.spotbugs:spotbugs-annotations:4.0.1 5 | com.google.code.findbugs:jsr305:3.0.2 6 | org.immutables:value:2.8.3 7 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 8 | space.npstr:annotations:0.0.2 9 | -------------------------------------------------------------------------------- /gradle/dependency-locks/default.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.jcabi:jcabi-log:0.14 5 | com.jcabi:jcabi-manifests:1.1 6 | io.netty:netty-buffer:4.1.48.Final 7 | io.netty:netty-codec-http2:4.1.48.Final 8 | io.netty:netty-codec-http:4.1.48.Final 9 | io.netty:netty-codec-socks:4.1.48.Final 10 | io.netty:netty-codec:4.1.48.Final 11 | io.netty:netty-common:4.1.48.Final 12 | io.netty:netty-handler-proxy:4.1.48.Final 13 | io.netty:netty-handler:4.1.48.Final 14 | io.netty:netty-resolver:4.1.48.Final 15 | io.netty:netty-transport-native-epoll:4.1.48.Final 16 | io.netty:netty-transport-native-unix-common:4.1.48.Final 17 | io.netty:netty-transport:4.1.48.Final 18 | io.projectreactor.netty:reactor-netty:0.9.6.RELEASE 19 | io.projectreactor:reactor-core:3.3.4.RELEASE 20 | org.json:json:20190722 21 | org.reactivestreams:reactive-streams:1.0.3 22 | org.slf4j:slf4j-api:1.7.30 23 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 24 | org.springframework:spring-beans:5.2.5.RELEASE 25 | org.springframework:spring-core:5.2.5.RELEASE 26 | org.springframework:spring-jcl:5.2.5.RELEASE 27 | org.springframework:spring-web:5.2.5.RELEASE 28 | org.springframework:spring-webflux:5.2.5.RELEASE 29 | -------------------------------------------------------------------------------- /gradle/dependency-locks/jacocoAgent.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.jacoco:org.jacoco.agent:0.8.5 5 | -------------------------------------------------------------------------------- /gradle/dependency-locks/jacocoAnt.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.jacoco:org.jacoco.agent:0.8.5 5 | org.jacoco:org.jacoco.ant:0.8.5 6 | org.jacoco:org.jacoco.core:0.8.5 7 | org.jacoco:org.jacoco.report:0.8.5 8 | org.ow2.asm:asm-analysis:7.2 9 | org.ow2.asm:asm-commons:7.2 10 | org.ow2.asm:asm-tree:7.2 11 | org.ow2.asm:asm:7.2 12 | -------------------------------------------------------------------------------- /gradle/dependency-locks/runtime.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /gradle/dependency-locks/runtimeClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.jcabi:jcabi-log:0.14 5 | com.jcabi:jcabi-manifests:1.1 6 | io.netty:netty-buffer:4.1.48.Final 7 | io.netty:netty-codec-http2:4.1.48.Final 8 | io.netty:netty-codec-http:4.1.48.Final 9 | io.netty:netty-codec-socks:4.1.48.Final 10 | io.netty:netty-codec:4.1.48.Final 11 | io.netty:netty-common:4.1.48.Final 12 | io.netty:netty-handler-proxy:4.1.48.Final 13 | io.netty:netty-handler:4.1.48.Final 14 | io.netty:netty-resolver:4.1.48.Final 15 | io.netty:netty-transport-native-epoll:4.1.48.Final 16 | io.netty:netty-transport-native-unix-common:4.1.48.Final 17 | io.netty:netty-transport:4.1.48.Final 18 | io.projectreactor.netty:reactor-netty:0.9.6.RELEASE 19 | io.projectreactor:reactor-core:3.3.4.RELEASE 20 | org.json:json:20190722 21 | org.reactivestreams:reactive-streams:1.0.3 22 | org.slf4j:slf4j-api:1.7.30 23 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 24 | org.springframework:spring-beans:5.2.5.RELEASE 25 | org.springframework:spring-core:5.2.5.RELEASE 26 | org.springframework:spring-jcl:5.2.5.RELEASE 27 | org.springframework:spring-web:5.2.5.RELEASE 28 | org.springframework:spring-webflux:5.2.5.RELEASE 29 | -------------------------------------------------------------------------------- /gradle/dependency-locks/testAnnotationProcessor.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /gradle/dependency-locks/testCompile.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /gradle/dependency-locks/testCompileClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | io.projectreactor:reactor-core:3.3.4.RELEASE 5 | org.apiguardian:apiguardian-api:1.1.0 6 | org.junit.jupiter:junit-jupiter-api:5.5.2 7 | org.junit.platform:junit-platform-commons:1.5.2 8 | org.opentest4j:opentest4j:1.2.0 9 | org.reactivestreams:reactive-streams:1.0.3 10 | org.slf4j:slf4j-api:1.7.30 11 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 12 | org.springframework:spring-beans:5.2.5.RELEASE 13 | org.springframework:spring-core:5.2.5.RELEASE 14 | org.springframework:spring-jcl:5.2.5.RELEASE 15 | org.springframework:spring-web:5.2.5.RELEASE 16 | org.springframework:spring-webflux:5.2.5.RELEASE 17 | -------------------------------------------------------------------------------- /gradle/dependency-locks/testCompileOnly.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /gradle/dependency-locks/testRuntime.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /gradle/dependency-locks/testRuntimeClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | ch.qos.logback:logback-classic:1.2.3 5 | ch.qos.logback:logback-core:1.2.3 6 | com.jcabi:jcabi-log:0.14 7 | com.jcabi:jcabi-manifests:1.1 8 | io.netty:netty-buffer:4.1.48.Final 9 | io.netty:netty-codec-http2:4.1.48.Final 10 | io.netty:netty-codec-http:4.1.48.Final 11 | io.netty:netty-codec-socks:4.1.48.Final 12 | io.netty:netty-codec:4.1.48.Final 13 | io.netty:netty-common:4.1.48.Final 14 | io.netty:netty-handler-proxy:4.1.48.Final 15 | io.netty:netty-handler:4.1.48.Final 16 | io.netty:netty-resolver:4.1.48.Final 17 | io.netty:netty-transport-native-epoll:4.1.48.Final 18 | io.netty:netty-transport-native-unix-common:4.1.48.Final 19 | io.netty:netty-transport:4.1.48.Final 20 | io.projectreactor.netty:reactor-netty:0.9.6.RELEASE 21 | io.projectreactor:reactor-core:3.3.4.RELEASE 22 | org.apiguardian:apiguardian-api:1.1.0 23 | org.json:json:20190722 24 | org.junit.jupiter:junit-jupiter-api:5.5.2 25 | org.junit.jupiter:junit-jupiter-engine:5.5.2 26 | org.junit.platform:junit-platform-commons:1.5.2 27 | org.junit.platform:junit-platform-engine:1.5.2 28 | org.opentest4j:opentest4j:1.2.0 29 | org.reactivestreams:reactive-streams:1.0.3 30 | org.slf4j:slf4j-api:1.7.30 31 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 32 | org.springframework:spring-beans:5.2.5.RELEASE 33 | org.springframework:spring-core:5.2.5.RELEASE 34 | org.springframework:spring-jcl:5.2.5.RELEASE 35 | org.springframework:spring-web:5.2.5.RELEASE 36 | org.springframework:spring-webflux:5.2.5.RELEASE 37 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schnapster/Magma/4f14645542d6d89969b39a4c07318ac6195e9119/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /impl/.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/annotationProcessor.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.immutables:value:2.8.3 5 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 6 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/archives.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/compile.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/compileClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.github.spotbugs:spotbugs-annotations:4.0.1 5 | com.google.code.findbugs:jsr305:3.0.2 6 | com.jcabi:jcabi-log:0.14 7 | com.jcabi:jcabi-manifests:1.1 8 | io.netty:netty-buffer:4.1.48.Final 9 | io.netty:netty-codec-http2:4.1.48.Final 10 | io.netty:netty-codec-http:4.1.48.Final 11 | io.netty:netty-codec-socks:4.1.48.Final 12 | io.netty:netty-codec:4.1.48.Final 13 | io.netty:netty-common:4.1.48.Final 14 | io.netty:netty-handler-proxy:4.1.48.Final 15 | io.netty:netty-handler:4.1.48.Final 16 | io.netty:netty-resolver:4.1.48.Final 17 | io.netty:netty-transport-native-epoll:4.1.48.Final 18 | io.netty:netty-transport-native-unix-common:4.1.48.Final 19 | io.netty:netty-transport:4.1.48.Final 20 | io.projectreactor.netty:reactor-netty:0.9.6.RELEASE 21 | io.projectreactor:reactor-core:3.3.4.RELEASE 22 | org.immutables:value:2.8.3 23 | org.json:json:20190722 24 | org.reactivestreams:reactive-streams:1.0.3 25 | org.slf4j:slf4j-api:1.7.30 26 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 27 | org.springframework:spring-beans:5.2.5.RELEASE 28 | org.springframework:spring-core:5.2.5.RELEASE 29 | org.springframework:spring-jcl:5.2.5.RELEASE 30 | org.springframework:spring-web:5.2.5.RELEASE 31 | org.springframework:spring-webflux:5.2.5.RELEASE 32 | space.npstr:annotations:0.0.2 33 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/compileOnly.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.github.spotbugs:spotbugs-annotations:4.0.1 5 | com.google.code.findbugs:jsr305:3.0.2 6 | org.immutables:value:2.8.3 7 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 8 | space.npstr:annotations:0.0.2 9 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/default.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.jcabi:jcabi-log:0.14 5 | com.jcabi:jcabi-manifests:1.1 6 | io.netty:netty-buffer:4.1.48.Final 7 | io.netty:netty-codec-http2:4.1.48.Final 8 | io.netty:netty-codec-http:4.1.48.Final 9 | io.netty:netty-codec-socks:4.1.48.Final 10 | io.netty:netty-codec:4.1.48.Final 11 | io.netty:netty-common:4.1.48.Final 12 | io.netty:netty-handler-proxy:4.1.48.Final 13 | io.netty:netty-handler:4.1.48.Final 14 | io.netty:netty-resolver:4.1.48.Final 15 | io.netty:netty-transport-native-epoll:4.1.48.Final 16 | io.netty:netty-transport-native-unix-common:4.1.48.Final 17 | io.netty:netty-transport:4.1.48.Final 18 | io.projectreactor.netty:reactor-netty:0.9.6.RELEASE 19 | io.projectreactor:reactor-core:3.3.4.RELEASE 20 | org.json:json:20190722 21 | org.reactivestreams:reactive-streams:1.0.3 22 | org.slf4j:slf4j-api:1.7.30 23 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 24 | org.springframework:spring-beans:5.2.5.RELEASE 25 | org.springframework:spring-core:5.2.5.RELEASE 26 | org.springframework:spring-jcl:5.2.5.RELEASE 27 | org.springframework:spring-web:5.2.5.RELEASE 28 | org.springframework:spring-webflux:5.2.5.RELEASE 29 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/jacocoAgent.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.jacoco:org.jacoco.agent:0.8.5 5 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/jacocoAnt.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.jacoco:org.jacoco.agent:0.8.5 5 | org.jacoco:org.jacoco.ant:0.8.5 6 | org.jacoco:org.jacoco.core:0.8.5 7 | org.jacoco:org.jacoco.report:0.8.5 8 | org.ow2.asm:asm-analysis:7.2 9 | org.ow2.asm:asm-commons:7.2 10 | org.ow2.asm:asm-tree:7.2 11 | org.ow2.asm:asm:7.2 12 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/runtime.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/runtimeClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.jcabi:jcabi-log:0.14 5 | com.jcabi:jcabi-manifests:1.1 6 | io.netty:netty-buffer:4.1.48.Final 7 | io.netty:netty-codec-http2:4.1.48.Final 8 | io.netty:netty-codec-http:4.1.48.Final 9 | io.netty:netty-codec-socks:4.1.48.Final 10 | io.netty:netty-codec:4.1.48.Final 11 | io.netty:netty-common:4.1.48.Final 12 | io.netty:netty-handler-proxy:4.1.48.Final 13 | io.netty:netty-handler:4.1.48.Final 14 | io.netty:netty-resolver:4.1.48.Final 15 | io.netty:netty-transport-native-epoll:4.1.48.Final 16 | io.netty:netty-transport-native-unix-common:4.1.48.Final 17 | io.netty:netty-transport:4.1.48.Final 18 | io.projectreactor.netty:reactor-netty:0.9.6.RELEASE 19 | io.projectreactor:reactor-core:3.3.4.RELEASE 20 | org.json:json:20190722 21 | org.reactivestreams:reactive-streams:1.0.3 22 | org.slf4j:slf4j-api:1.7.30 23 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 24 | org.springframework:spring-beans:5.2.5.RELEASE 25 | org.springframework:spring-core:5.2.5.RELEASE 26 | org.springframework:spring-jcl:5.2.5.RELEASE 27 | org.springframework:spring-web:5.2.5.RELEASE 28 | org.springframework:spring-webflux:5.2.5.RELEASE 29 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/testAnnotationProcessor.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/testCompile.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/testCompileClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | com.jcabi:jcabi-log:0.14 5 | com.jcabi:jcabi-manifests:1.1 6 | io.netty:netty-buffer:4.1.48.Final 7 | io.netty:netty-codec-http2:4.1.48.Final 8 | io.netty:netty-codec-http:4.1.48.Final 9 | io.netty:netty-codec-socks:4.1.48.Final 10 | io.netty:netty-codec:4.1.48.Final 11 | io.netty:netty-common:4.1.48.Final 12 | io.netty:netty-handler-proxy:4.1.48.Final 13 | io.netty:netty-handler:4.1.48.Final 14 | io.netty:netty-resolver:4.1.48.Final 15 | io.netty:netty-transport-native-epoll:4.1.48.Final 16 | io.netty:netty-transport-native-unix-common:4.1.48.Final 17 | io.netty:netty-transport:4.1.48.Final 18 | io.projectreactor.netty:reactor-netty:0.9.6.RELEASE 19 | io.projectreactor:reactor-core:3.3.4.RELEASE 20 | org.apiguardian:apiguardian-api:1.1.0 21 | org.json:json:20190722 22 | org.junit.jupiter:junit-jupiter-api:5.5.2 23 | org.junit.platform:junit-platform-commons:1.5.2 24 | org.opentest4j:opentest4j:1.2.0 25 | org.reactivestreams:reactive-streams:1.0.3 26 | org.slf4j:slf4j-api:1.7.30 27 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 28 | org.springframework:spring-beans:5.2.5.RELEASE 29 | org.springframework:spring-core:5.2.5.RELEASE 30 | org.springframework:spring-jcl:5.2.5.RELEASE 31 | org.springframework:spring-web:5.2.5.RELEASE 32 | org.springframework:spring-webflux:5.2.5.RELEASE 33 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/testCompileOnly.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/testRuntime.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /impl/gradle/dependency-locks/testRuntimeClasspath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | ch.qos.logback:logback-classic:1.2.3 5 | ch.qos.logback:logback-core:1.2.3 6 | com.jcabi:jcabi-log:0.14 7 | com.jcabi:jcabi-manifests:1.1 8 | io.netty:netty-buffer:4.1.48.Final 9 | io.netty:netty-codec-http2:4.1.48.Final 10 | io.netty:netty-codec-http:4.1.48.Final 11 | io.netty:netty-codec-socks:4.1.48.Final 12 | io.netty:netty-codec:4.1.48.Final 13 | io.netty:netty-common:4.1.48.Final 14 | io.netty:netty-handler-proxy:4.1.48.Final 15 | io.netty:netty-handler:4.1.48.Final 16 | io.netty:netty-resolver:4.1.48.Final 17 | io.netty:netty-transport-native-epoll:4.1.48.Final 18 | io.netty:netty-transport-native-unix-common:4.1.48.Final 19 | io.netty:netty-transport:4.1.48.Final 20 | io.projectreactor.netty:reactor-netty:0.9.6.RELEASE 21 | io.projectreactor:reactor-core:3.3.4.RELEASE 22 | org.apiguardian:apiguardian-api:1.1.0 23 | org.json:json:20190722 24 | org.junit.jupiter:junit-jupiter-api:5.5.2 25 | org.junit.jupiter:junit-jupiter-engine:5.5.2 26 | org.junit.platform:junit-platform-commons:1.5.2 27 | org.junit.platform:junit-platform-engine:1.5.2 28 | org.opentest4j:opentest4j:1.2.0 29 | org.reactivestreams:reactive-streams:1.0.3 30 | org.slf4j:slf4j-api:1.7.30 31 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 32 | org.springframework:spring-beans:5.2.5.RELEASE 33 | org.springframework:spring-core:5.2.5.RELEASE 34 | org.springframework:spring-jcl:5.2.5.RELEASE 35 | org.springframework:spring-web:5.2.5.RELEASE 36 | org.springframework:spring-webflux:5.2.5.RELEASE 37 | -------------------------------------------------------------------------------- /impl/src/main/java/net/dv8tion/jda/api/audio/AudioPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2018 Austin Keener & Michael Ritter & Florian Spieß 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.dv8tion.jda.api.audio; 18 | 19 | import com.iwebpp.crypto.TweetNaclFast; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import javax.annotation.Nonnull; 24 | import java.nio.Buffer; 25 | import java.nio.ByteBuffer; 26 | 27 | /** 28 | * Represents the contents of a audio packet that was either received from Discord or 29 | * will be sent to discord. 30 | */ 31 | public class AudioPacket 32 | { 33 | private static final Logger log = LoggerFactory.getLogger(AudioPacket.class); 34 | 35 | public static final int RTP_HEADER_BYTE_LENGTH = 12; 36 | 37 | /** 38 | * Bit index 0 and 1 represent the RTP Protocol version used. Discord uses the latest RTP protocol version, 2.
39 | * Bit index 2 represents whether or not we pad. Opus uses an internal padding system, so RTP padding is not used.
40 | * Bit index 3 represents if we use extensions. Discord does not use RTP extensions.
41 | * Bit index 4 to 7 represent the CC or CSRC count. CSRC is Combined SSRC. Discord doesn't combine audio streams, 42 | * so the Combined count will always be 0 (binary: 0000).
43 | * This byte should always be the same, no matter the library implementation. 44 | */ 45 | public static final byte RTP_VERSION_PAD_EXTEND = (byte) 0x80; //Binary: 1000 0000 46 | 47 | /** 48 | * This is Discord's RTP Profile Payload type.
49 | * I've yet to find actual documentation on what the bits inside this value represent.
50 | * As far as I can tell, this byte will always be the same, no matter the library implementation.
51 | */ 52 | public static final byte RTP_PAYLOAD_TYPE = (byte) 0x78; //Binary: 0100 1000 53 | 54 | private final char seq; 55 | private final int timestamp; 56 | private final int ssrc; 57 | private final ByteBuffer encodedAudio; 58 | private final byte[] rawPacket; 59 | 60 | public AudioPacket(final char seq, final int timestamp, final int ssrc, final ByteBuffer encodedAudio) 61 | { 62 | this.seq = seq; 63 | this.ssrc = ssrc; 64 | this.timestamp = timestamp; 65 | this.encodedAudio = encodedAudio; 66 | this.rawPacket = generateRawPacket(seq, timestamp, ssrc, encodedAudio); 67 | } 68 | 69 | public byte[] getNoncePadded() 70 | { 71 | final byte[] nonce = new byte[TweetNaclFast.SecretBox.nonceLength]; 72 | //The first 12 bytes are the rawPacket are the RTP Discord Nonce. 73 | System.arraycopy(this.rawPacket, 0, nonce, 0, RTP_HEADER_BYTE_LENGTH); 74 | return nonce; 75 | } 76 | 77 | //this may reallocate the passed bytebuffer if it is too small 78 | public ByteBuffer asEncryptedPacket(final ByteBuffer buffer, final byte[] secretKey, @Nonnull final byte[] nonce, final int nonceLength) 79 | { 80 | ByteBuffer outputBuffer = buffer; 81 | //Xsalsa20's Nonce is 24 bytes long, however RTP (and consequently Discord)'s nonce is a different length 82 | // so we need to create a 24 byte array, and copy the nonce into it. 83 | // we will leave the extra bytes as nulls. (Java sets non-populated bytes as 0). 84 | byte[] extendedNonce = nonce; 85 | if (nonceLength == 0) { 86 | extendedNonce = getNoncePadded(); 87 | } 88 | final byte[] array = encodedAudio.array(); 89 | final int arrayOffset = encodedAudio.arrayOffset(); 90 | final int length = encodedAudio.remaining(); 91 | 92 | //Create our SecretBox encoder with the secretKey provided by Discord. 93 | final TweetNaclFast.SecretBox boxer = new TweetNaclFast.SecretBox(secretKey); 94 | final byte[] encryptedAudio = boxer.box(array, arrayOffset, length, extendedNonce); 95 | outputBuffer.clear(); 96 | final int capacity = RTP_HEADER_BYTE_LENGTH + encryptedAudio.length + nonceLength; 97 | if (capacity > outputBuffer.remaining()) { 98 | log.trace("Allocating byte buffer with capacity " + capacity); 99 | outputBuffer = ByteBuffer.allocate(capacity); 100 | } 101 | populateBuffer(this.seq, this.timestamp, this.ssrc, ByteBuffer.wrap(encryptedAudio), outputBuffer); 102 | if (nonceLength > 0) { 103 | outputBuffer.put(nonce, 0, nonceLength); 104 | } 105 | 106 | ((Buffer) outputBuffer).flip(); 107 | return outputBuffer; 108 | } 109 | 110 | private static byte[] generateRawPacket(final char seq, final int timestamp, final int ssrc, final ByteBuffer data) 111 | { 112 | final ByteBuffer buffer = ByteBuffer.allocate(RTP_HEADER_BYTE_LENGTH + data.remaining()); 113 | populateBuffer(seq, timestamp, ssrc, data, buffer); 114 | return buffer.array(); 115 | } 116 | 117 | private static void populateBuffer(final char seq, final int timestamp, final int ssrc, final ByteBuffer data, final ByteBuffer buffer) 118 | { 119 | buffer.put(RTP_VERSION_PAD_EXTEND); 120 | buffer.put(RTP_PAYLOAD_TYPE); 121 | buffer.putChar(seq); 122 | buffer.putInt(timestamp); 123 | buffer.putInt(ssrc); 124 | buffer.put(data); 125 | ((Buffer) data).flip(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/AudioStackLifecyclePipeline.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl; 18 | 19 | import edu.umd.cs.findbugs.annotations.CheckReturnValue; 20 | import net.dv8tion.jda.api.audio.factory.IAudioSendFactory; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | import reactor.core.publisher.BaseSubscriber; 24 | import space.npstr.magma.api.MagmaMember; 25 | import space.npstr.magma.api.MagmaWebsocketConnectionState; 26 | import space.npstr.magma.api.Member; 27 | import space.npstr.magma.api.WebsocketConnectionState; 28 | import space.npstr.magma.api.event.MagmaEvent; 29 | import space.npstr.magma.impl.connections.AudioConnection; 30 | import space.npstr.magma.impl.connections.AudioWebSocket; 31 | import space.npstr.magma.impl.connections.hax.ClosingWebSocketClient; 32 | import space.npstr.magma.impl.events.audio.lifecycle.CloseWebSocket; 33 | import space.npstr.magma.impl.events.audio.lifecycle.ConnectWebSocketLcEvent; 34 | import space.npstr.magma.impl.events.audio.lifecycle.LifecycleEvent; 35 | import space.npstr.magma.impl.events.audio.lifecycle.Shutdown; 36 | import space.npstr.magma.impl.events.audio.lifecycle.UpdateSendHandler; 37 | import space.npstr.magma.impl.events.audio.lifecycle.UpdateSpeakingMode; 38 | import space.npstr.magma.impl.events.audio.lifecycle.VoiceServerUpdate; 39 | import space.npstr.magma.impl.immutables.ImmutableSessionInfo; 40 | 41 | import java.net.DatagramSocket; 42 | import java.util.List; 43 | import java.util.Map; 44 | import java.util.concurrent.ConcurrentHashMap; 45 | import java.util.function.Consumer; 46 | import java.util.function.Function; 47 | import java.util.stream.Collectors; 48 | 49 | /** 50 | * Created by napster on 22.04.18. 51 | *

52 | * This class manages the lifecycles of various AudioStack objects. 53 | *

54 | * The {@link AudioStack} consists of: 55 | *

61 | * 62 | *

Lifecycle Events

63 | * 82 | */ 83 | public class AudioStackLifecyclePipeline extends BaseSubscriber { 84 | 85 | private static final Logger log = LoggerFactory.getLogger(AudioStackLifecyclePipeline.class); 86 | 87 | // userId <-> guildId <-> audio stack 88 | private final Map> audioStacks = new ConcurrentHashMap<>(); 89 | 90 | private final Function sendFactoryProvider; 91 | private final ClosingWebSocketClient webSocketClient; 92 | private final Consumer apiEventConsumer; 93 | private final DatagramSocket udpSocket; 94 | 95 | public AudioStackLifecyclePipeline(final Function sendFactoryProvider, 96 | final ClosingWebSocketClient webSocketClient, 97 | final Consumer apiEventConsumer, 98 | final DatagramSocket udpSocket) { 99 | this.sendFactoryProvider = sendFactoryProvider; 100 | this.webSocketClient = webSocketClient; 101 | this.apiEventConsumer = apiEventConsumer; 102 | this.udpSocket = udpSocket; 103 | } 104 | 105 | @Override 106 | protected void hookOnNext(final LifecycleEvent event) { 107 | if (event instanceof VoiceServerUpdate) { 108 | final VoiceServerUpdate voiceServerUpdate = (VoiceServerUpdate) event; 109 | this.getAudioStack(event) 110 | .next(ConnectWebSocketLcEvent.builder() 111 | .sessionInfo(ImmutableSessionInfo.builder() 112 | .voiceServerUpdate(voiceServerUpdate) 113 | .build()) 114 | .build() 115 | ); 116 | } else if (event instanceof UpdateSendHandler) { 117 | this.getAudioStack(event) 118 | .next(event); 119 | } else if (event instanceof CloseWebSocket) { 120 | //pass it on 121 | this.apiEventConsumer.accept(((CloseWebSocket) event).getApiEvent()); 122 | this.getAudioStack(event) 123 | .next(event); 124 | } else if (event instanceof Shutdown) { 125 | this.dispose(); 126 | 127 | this.audioStacks.values().stream().flatMap(map -> map.values().stream()).forEach( 128 | audioStack -> audioStack.next(event) 129 | ); 130 | } else if (event instanceof UpdateSpeakingMode) { 131 | this.getAudioStack(event) 132 | .next(event); 133 | } else { 134 | log.warn("Unhandled lifecycle event of class {}", event.getClass().getSimpleName()); 135 | } 136 | } 137 | 138 | @CheckReturnValue 139 | public List getAudioConnectionStates() { 140 | return this.audioStacks.entrySet().stream() 141 | .flatMap(outerEntry -> { 142 | final String userId = outerEntry.getKey(); 143 | return outerEntry.getValue().entrySet().stream() 144 | .map(innerEntry -> { 145 | final String guildId = innerEntry.getKey(); 146 | final AudioStack audioStack = innerEntry.getValue(); 147 | return MagmaWebsocketConnectionState.builder() 148 | .member(MagmaMember.builder() 149 | .userId(userId) 150 | .guildId(guildId) 151 | .build()) 152 | .phase(audioStack.getConnectionPhase()) 153 | .build(); 154 | }); 155 | }) 156 | .collect(Collectors.toList()); 157 | } 158 | 159 | @CheckReturnValue 160 | @SuppressWarnings("squid:S00117") 161 | private AudioStack getAudioStack(final LifecycleEvent lifecycleEvent) { 162 | return this.audioStacks 163 | .computeIfAbsent(lifecycleEvent.getUserId(), __ -> new ConcurrentHashMap<>()) 164 | .computeIfAbsent(lifecycleEvent.getGuildId(), __ -> 165 | new AudioStack(lifecycleEvent.getMember(), 166 | this.sendFactoryProvider.apply(lifecycleEvent.getMember()), 167 | this.webSocketClient, 168 | this.apiEventConsumer, 169 | this.udpSocket)); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/EncryptionMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl; 18 | 19 | import org.json.JSONArray; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | import java.util.Comparator; 26 | import java.util.List; 27 | import java.util.Optional; 28 | 29 | public enum EncryptionMode { 30 | 31 | XSALSA20_POLY1305_LITE(30), // uses 4 byte nonce instead of 24 bytes 32 | XSALSA20_POLY1305_SUFFIX(20), // "official" implementation using random 24 byte nonce 33 | XSALSA20_POLY1305(10); // unofficial implementation using time stamps (?) as nonce (24 bytes total) 34 | 35 | private static final Logger log = LoggerFactory.getLogger(EncryptionMode.class); 36 | private static final Comparator preferenceComparator = Comparator.comparingInt(EncryptionMode::getPreference).reversed(); 37 | 38 | private final int preference; 39 | private final String key; 40 | 41 | EncryptionMode(final int preference) { 42 | this.preference = preference; 43 | this.key = this.name().toLowerCase(); 44 | } 45 | 46 | /** 47 | * @return the key that discord recognizes this mode under 48 | */ 49 | public String getKey() { 50 | return this.key; 51 | } 52 | 53 | /** 54 | * @return the preference indicator 55 | */ 56 | public int getPreference() 57 | { 58 | return preference; 59 | } 60 | 61 | /** 62 | * @return The encryption mode corresponding to the given input, or nothing. 63 | */ 64 | public static Optional parse(final String input) { 65 | try { 66 | return Optional.of(EncryptionMode.valueOf(input.toUpperCase())); 67 | } catch (final IllegalArgumentException e) { 68 | log.debug("Could not parse encryption mode: {}", input); 69 | } 70 | 71 | return Optional.empty(); 72 | } 73 | 74 | /** 75 | * @return parse a JSONArray into a list of encryption modes 76 | */ 77 | public static List fromJson(final JSONArray array) { 78 | final List result = new ArrayList<>(); 79 | for (final Object o : array) { 80 | try { 81 | parse((String) o).ifPresent(result::add); 82 | } catch (final IllegalArgumentException ignored) { 83 | //ignored 84 | } 85 | } 86 | return result; 87 | } 88 | 89 | /** 90 | * @return parse a JSONArray into a list of encryption modes 91 | */ 92 | public static Optional getPreferredMode(final Collection encryptionModes) { 93 | if (encryptionModes.isEmpty()) { 94 | log.warn("Can not pick a preferred encryption mode from an empty collection"); 95 | return Optional.empty(); 96 | } 97 | final List sort = new ArrayList<>(encryptionModes); 98 | sort.sort(preferenceComparator); 99 | return Optional.of(sort.get(0)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/Magma.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl; 18 | 19 | import edu.umd.cs.findbugs.annotations.Nullable; 20 | import java.net.DatagramSocket; 21 | import java.net.SocketException; 22 | import java.util.List; 23 | import java.util.Optional; 24 | import java.util.Set; 25 | import java.util.function.Function; 26 | import java.util.logging.Level; 27 | import net.dv8tion.jda.api.audio.AudioSendHandler; 28 | import net.dv8tion.jda.api.audio.factory.IAudioSendFactory; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | import reactor.core.publisher.Flux; 32 | import reactor.core.publisher.FluxSink; 33 | import reactor.core.publisher.UnicastProcessor; 34 | import reactor.core.scheduler.Schedulers; 35 | import space.npstr.magma.api.MagmaApi; 36 | import space.npstr.magma.api.Member; 37 | import space.npstr.magma.api.ServerUpdate; 38 | import space.npstr.magma.api.SpeakingMode; 39 | import space.npstr.magma.api.WebsocketConnectionState; 40 | import space.npstr.magma.api.event.MagmaEvent; 41 | import space.npstr.magma.api.event.WebSocketClosedApiEvent; 42 | import space.npstr.magma.impl.connections.hax.ClosingReactorNettyWebSocketClient; 43 | import space.npstr.magma.impl.connections.hax.ClosingWebSocketClient; 44 | import space.npstr.magma.impl.events.audio.lifecycle.CloseWebSocketLcEvent; 45 | import space.npstr.magma.impl.events.audio.lifecycle.LifecycleEvent; 46 | import space.npstr.magma.impl.events.audio.lifecycle.Shutdown; 47 | import space.npstr.magma.impl.events.audio.lifecycle.UpdateSendHandlerLcEvent; 48 | import space.npstr.magma.impl.events.audio.lifecycle.UpdateSpeakingModeLcEvent; 49 | import space.npstr.magma.impl.events.audio.lifecycle.VoiceServerUpdateLcEvent; 50 | 51 | public class Magma implements MagmaApi { 52 | 53 | private static final Logger log = LoggerFactory.getLogger(Magma.class); 54 | 55 | private final FluxSink lifecycleSink; 56 | @Nullable 57 | private FluxSink apiEventSink = null; 58 | private final Flux apiEventFlux = Flux.create(sink -> this.apiEventSink = sink); 59 | private final AudioStackLifecyclePipeline lifecyclePipeline; 60 | private final DatagramSocket udpSocket; 61 | 62 | /** 63 | * @see MagmaApi 64 | */ 65 | public Magma(final Function sendFactoryProvider) { 66 | final ClosingWebSocketClient webSocketClient = new ClosingReactorNettyWebSocketClient(); 67 | try { 68 | this.udpSocket = new DatagramSocket(); 69 | } catch (final SocketException e) { 70 | throw new RuntimeException("Failed to set up datagram socket", e); 71 | } 72 | 73 | this.lifecyclePipeline = new AudioStackLifecyclePipeline( 74 | sendFactoryProvider, 75 | webSocketClient, 76 | magmaEvent -> { 77 | if (this.apiEventSink != null) this.apiEventSink.next(magmaEvent); 78 | }, 79 | this.udpSocket 80 | ); 81 | 82 | final UnicastProcessor processor = UnicastProcessor.create(); 83 | this.lifecycleSink = processor.sink(); 84 | processor 85 | .log(log.getName(), Level.FINEST) //FINEST = TRACE 86 | .publishOn(Schedulers.parallel()) 87 | .subscribe(this.lifecyclePipeline); 88 | } 89 | 90 | // ################################################################################ 91 | // # Public API 92 | // ################################################################################ 93 | 94 | @Override 95 | public DatagramSocket getDatagramSocket() { 96 | return this.udpSocket; 97 | } 98 | 99 | @Override 100 | public void shutdown() { 101 | this.lifecycleSink.next(Shutdown.INSTANCE); 102 | if (this.apiEventSink != null) this.apiEventSink.complete(); 103 | this.udpSocket.close(); 104 | } 105 | 106 | @Override 107 | public Flux getEventStream() { 108 | return this.apiEventFlux; 109 | } 110 | 111 | @Override 112 | public void provideVoiceServerUpdate(final Member member, final ServerUpdate serverUpdate) { 113 | this.lifecycleSink.next(VoiceServerUpdateLcEvent.builder() 114 | .member(member) 115 | .sessionId(serverUpdate.getSessionId()) 116 | .endpoint(serverUpdate.getEndpoint().replace(":80", "")) //Strip the port from the endpoint. 117 | .token(serverUpdate.getToken()) 118 | .build()); 119 | } 120 | 121 | @Override 122 | public void setSendHandler(final Member member, final AudioSendHandler sendHandler) { 123 | this.updateSendHandler(member, sendHandler); 124 | } 125 | 126 | @Override 127 | public void setSpeakingMode(final Member member, @Nullable final Set mode) { 128 | this.lifecycleSink.next(UpdateSpeakingModeLcEvent.builder() 129 | .member(member) 130 | .speakingModes(mode) 131 | .build()); 132 | } 133 | 134 | @Override 135 | public void removeSendHandler(final Member member) { 136 | this.updateSendHandler(member, null); 137 | } 138 | 139 | @Override 140 | public void closeConnection(final Member member) { 141 | this.lifecycleSink.next(CloseWebSocketLcEvent.builder() 142 | .member(member) 143 | .apiEvent(WebSocketClosedApiEvent.builder() 144 | .member(member) 145 | .closeCode(1000) 146 | .reason("Closed by client") 147 | .isByRemote(false) 148 | .build()) 149 | .build()); 150 | } 151 | 152 | @Override 153 | public List getAudioConnectionStates() { 154 | return this.lifecyclePipeline.getAudioConnectionStates(); 155 | } 156 | 157 | // ################################################################################ 158 | // # Internals 159 | // ################################################################################ 160 | 161 | private void updateSendHandler(final Member member, @Nullable final AudioSendHandler sendHandler) { 162 | this.lifecycleSink.next(UpdateSendHandlerLcEvent.builder() 163 | .member(member) 164 | .audioSendHandler(Optional.ofNullable(sendHandler)) 165 | .build()); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/MagmaVersionProvider.java: -------------------------------------------------------------------------------- 1 | package space.npstr.magma.impl; 2 | 3 | import com.jcabi.manifests.Manifests; 4 | 5 | public class MagmaVersionProvider { 6 | 7 | // See gradle build file where this is written into the manifest 8 | private static final String MAGMA_VERSION_KEY = "Magma-Version"; 9 | 10 | private static final String FALLBACK_VERSION = "unknown version"; 11 | 12 | public String getVersion() { 13 | 14 | if (!Manifests.exists(MAGMA_VERSION_KEY)) { 15 | return FALLBACK_VERSION; 16 | } 17 | 18 | return Manifests.read(MAGMA_VERSION_KEY); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/connections/AudioWebSocketSessionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.connections; 18 | 19 | import edu.umd.cs.findbugs.annotations.Nullable; 20 | import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus; 21 | import org.json.JSONObject; 22 | import org.reactivestreams.Subscriber; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | import org.springframework.web.reactive.socket.WebSocketHandler; 26 | import org.springframework.web.reactive.socket.WebSocketMessage; 27 | import org.springframework.web.reactive.socket.WebSocketSession; 28 | import reactor.core.publisher.BaseSubscriber; 29 | import reactor.core.publisher.Flux; 30 | import reactor.core.publisher.FluxSink; 31 | import reactor.core.publisher.Mono; 32 | import reactor.core.publisher.UnicastProcessor; 33 | import reactor.core.scheduler.Schedulers; 34 | import space.npstr.magma.impl.connections.hax.ClosingReactorNettyWebSocketSession; 35 | import space.npstr.magma.impl.events.audio.ws.OpCode; 36 | import space.npstr.magma.impl.events.audio.ws.in.InboundWsEvent; 37 | import space.npstr.magma.impl.events.audio.ws.out.OutboundWsEvent; 38 | 39 | import java.util.Objects; 40 | import java.util.concurrent.atomic.AtomicReference; 41 | import java.util.logging.Level; 42 | 43 | /** 44 | * Created by napster on 21.04.18. 45 | */ 46 | public class AudioWebSocketSessionHandler extends BaseSubscriber implements WebSocketHandler { 47 | private static final Logger log = LoggerFactory.getLogger(AudioWebSocketSessionHandler.class); 48 | private final Subscriber inbound; 49 | 50 | private final IntermediaryPipeHolder pipes = new IntermediaryPipeHolder(); 51 | @Nullable 52 | private WebSocketSession session; 53 | 54 | /** 55 | * @param inbound 56 | * Subscriber to the events we will receive from Discord 57 | */ 58 | public AudioWebSocketSessionHandler(final Subscriber inbound) { 59 | this.prepareConnect(); 60 | this.inbound = inbound; 61 | } 62 | 63 | /** 64 | * Close the session of this handler, if there is any. 65 | */ 66 | public void close() { 67 | if (this.session != null) { 68 | this.session.close() 69 | .publishOn(Schedulers.parallel()) 70 | .subscribe(); 71 | } 72 | } 73 | 74 | /** 75 | * Call this when planning to reuse this handler for another session. 76 | * We need to replace the intermediary processor so that the new session can be subscribed to the original 77 | * {@code Flux outbound} constructor parameter. 78 | *

79 | * Any outbound events buffered in the old processor will be lost upon calling this, which is ok, 80 | * given that this method is expected to be called when the connection has been closed. 81 | */ 82 | public void prepareConnect() { 83 | final UnicastProcessor intermediaryProcessor = UnicastProcessor.create(); 84 | this.pipes.setIntermediaryOutboundSink(intermediaryProcessor.sink()); 85 | this.pipes.setIntermediaryOutboundFlux(intermediaryProcessor); 86 | } 87 | 88 | @Override 89 | @SuppressWarnings("squid:CommentedOutCodeLine") 90 | public Mono handle(final WebSocketSession session) { 91 | 92 | // * * * 93 | // ATTENTION: Live testing code for resuming. Do not commit uncommented 94 | // Mono.delay(Duration.ofSeconds(30)) 95 | // .subscribe(tick -> { 96 | // session.close(new CloseStatus(CloseCode.VOICE_SERVER_CRASHED, "lol")); 97 | // ((AudioWebSocket) this.inbound).hookOnNext(WebSocketClosedWsEvent.builder() 98 | // .code(CloseCode.VOICE_SERVER_CRASHED) 99 | // .reason("lol") 100 | // .build()); 101 | // }); 102 | // * * * 103 | 104 | this.session = session; 105 | log.trace("Handshake: {}", session.getHandshakeInfo()); 106 | 107 | Flux messages = session.receive() 108 | .map(WebSocketMessage::getPayloadAsText); 109 | 110 | ClosingReactorNettyWebSocketSession reactorNettyWebSocketSession = (ClosingReactorNettyWebSocketSession) session; 111 | Mono closeMessage = reactorNettyWebSocketSession.getDelegate().getInbound().receiveCloseStatus() 112 | .map(this::toMagmaWebSocketEventPayload); 113 | 114 | Flux.mergeDelayError(1, messages, closeMessage) 115 | .log(log.getName() + ".>>>", Level.FINEST) //FINEST = TRACE 116 | .map(InboundWsEvent::from) 117 | .doOnTerminate(() -> log.trace("Receiving terminated")) 118 | .publishOn(Schedulers.parallel()) 119 | .subscribe(this.inbound); 120 | 121 | return session 122 | .send(this.pipes.getIntermediaryOutboundFlux() 123 | .map(OutboundWsEvent::asMessage) 124 | .log(log.getName() + ".<<<", Level.FINEST) //FINEST = TRACE 125 | .map(session::textMessage) 126 | ) 127 | .doOnTerminate(() -> log.trace("Sending terminated")); 128 | } 129 | 130 | @Override 131 | protected void hookOnNext(final OutboundWsEvent event) { 132 | this.pipes.getIntermediaryOutboundSink().next(event); 133 | } 134 | 135 | /** 136 | * Transform a websocket close status into a payload that will be parsed in {@link space.npstr.magma.impl.events.audio.ws.in.InboundWsEvent#from} 137 | */ 138 | private String toMagmaWebSocketEventPayload(WebSocketCloseStatus closeStatus) { 139 | return new JSONObject() 140 | .put("op", OpCode.WEBSOCKET_CLOSE) 141 | .put("d", new JSONObject() 142 | .put("code", closeStatus.code()) 143 | .put("reason", closeStatus.reasonText())) 144 | .toString(); 145 | } 146 | 147 | /** 148 | * Helper class to take care of volatile & null checks 149 | */ 150 | private static class IntermediaryPipeHolder { 151 | 152 | private final AtomicReference> intermediaryOutboundFlux = new AtomicReference<>(); 153 | private final AtomicReference> intermediaryOutboundSink = new AtomicReference<>(); 154 | 155 | private Flux getIntermediaryOutboundFlux() { 156 | return Objects.requireNonNull(this.intermediaryOutboundFlux.get(), "Using the intermediary outbound flux before it has been prepared"); 157 | } 158 | 159 | private void setIntermediaryOutboundFlux(final Flux intermediaryOutboundFlux) { 160 | this.intermediaryOutboundFlux.set(intermediaryOutboundFlux); 161 | } 162 | 163 | private FluxSink getIntermediaryOutboundSink() { 164 | return Objects.requireNonNull(this.intermediaryOutboundSink.get(), "Using the intermediary outbound sink before it has been prepared"); 165 | } 166 | 167 | private void setIntermediaryOutboundSink(final FluxSink intermediaryOutboundSink) { 168 | this.intermediaryOutboundSink.set(intermediaryOutboundSink); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/connections/hax/ClosingReactorNettyWebSocketClient.java: -------------------------------------------------------------------------------- 1 | package space.npstr.magma.impl.connections.hax; 2 | 3 | import java.net.URI; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.core.io.buffer.NettyDataBufferFactory; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.util.StringUtils; 9 | import org.springframework.web.reactive.socket.HandshakeInfo; 10 | import org.springframework.web.reactive.socket.WebSocketHandler; 11 | import org.springframework.web.reactive.socket.WebSocketSession; 12 | import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; 13 | import reactor.core.publisher.Mono; 14 | import reactor.netty.http.websocket.WebsocketInbound; 15 | 16 | /** 17 | * Allow our {@link space.npstr.magma.impl.connections.AudioWebSocketSessionHandler} us to get its hands on the close code. 18 | * 19 | * Plugs in our custom {@link ClosingReactorNettyWebSocketSession}. 20 | * 21 | * Rest of the file is copied from the superclass(es) that is necessary to make that work. 22 | */ 23 | public class ClosingReactorNettyWebSocketClient extends ReactorNettyWebSocketClient implements ClosingWebSocketClient { 24 | 25 | private static final Logger logger = LoggerFactory.getLogger(ClosingReactorNettyWebSocketClient.class); 26 | 27 | @Override 28 | public Mono execute(URI url, HttpHeaders requestHeaders, WebSocketHandler handler) { 29 | return getHttpClient() 30 | .headers(nettyHeaders -> setNettyHeaders(requestHeaders, nettyHeaders)) 31 | .websocket(StringUtils.collectionToCommaDelimitedString(handler.getSubProtocols())) 32 | .uri(url.toString()) 33 | .handle((inbound, outbound) -> { 34 | HttpHeaders responseHeaders = toHttpHeaders(inbound); 35 | String protocol = responseHeaders.getFirst("Sec-WebSocket-Protocol"); 36 | HandshakeInfo info = new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol); 37 | NettyDataBufferFactory factory = new NettyDataBufferFactory(outbound.alloc()); 38 | 39 | // * * * * * 40 | // plug in our custom websocket session 41 | 42 | WebSocketSession session = new ClosingReactorNettyWebSocketSession(inbound, outbound, info, factory); 43 | 44 | // * * * * * 45 | if (logger.isDebugEnabled()) { 46 | logger.debug("Started session '" + session.getId() + "' for " + url); 47 | } 48 | return handler.handle(session); 49 | }) 50 | .doOnRequest(n -> { 51 | if (logger.isDebugEnabled()) { 52 | logger.debug("Connecting to " + url); 53 | } 54 | }) 55 | .next(); 56 | } 57 | 58 | private void setNettyHeaders(HttpHeaders httpHeaders, io.netty.handler.codec.http.HttpHeaders nettyHeaders) { 59 | httpHeaders.forEach(nettyHeaders::set); 60 | } 61 | 62 | private HttpHeaders toHttpHeaders(WebsocketInbound inbound) { 63 | HttpHeaders headers = new HttpHeaders(); 64 | io.netty.handler.codec.http.HttpHeaders nettyHeaders = inbound.headers(); 65 | nettyHeaders.forEach(entry -> { 66 | String name = entry.getKey(); 67 | headers.put(name, nettyHeaders.getAll(name)); 68 | }); 69 | return headers; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/connections/hax/ClosingReactorNettyWebSocketSession.java: -------------------------------------------------------------------------------- 1 | package space.npstr.magma.impl.connections.hax; 2 | 3 | import org.springframework.core.io.buffer.NettyDataBufferFactory; 4 | import org.springframework.web.reactive.socket.HandshakeInfo; 5 | import org.springframework.web.reactive.socket.adapter.ReactorNettyWebSocketSession; 6 | import reactor.netty.http.websocket.WebsocketInbound; 7 | import reactor.netty.http.websocket.WebsocketOutbound; 8 | 9 | /** 10 | * Allow our {@link space.npstr.magma.impl.connections.AudioWebSocketSessionHandler} us to get its hands on the close code. 11 | */ 12 | public class ClosingReactorNettyWebSocketSession extends ReactorNettyWebSocketSession { 13 | 14 | public ClosingReactorNettyWebSocketSession( 15 | WebsocketInbound inbound, 16 | WebsocketOutbound outbound, 17 | HandshakeInfo info, 18 | NettyDataBufferFactory bufferFactory 19 | ) { 20 | super(inbound, outbound, info, bufferFactory); 21 | } 22 | 23 | @Override 24 | public WebSocketConnection getDelegate() { 25 | return super.getDelegate(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/connections/hax/ClosingWebSocketClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.connections.hax; 18 | 19 | import org.springframework.web.reactive.socket.client.WebSocketClient; 20 | 21 | /** 22 | * Created by napster on 21.06.18. 23 | *

24 | * Mark a class as enhanced by our code to properly relay the close events. 25 | */ 26 | public interface ClosingWebSocketClient extends WebSocketClient {} 27 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/connections/hax/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.connections.hax; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/connections/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.connections; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/ConnectionEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.conn; 18 | 19 | /** 20 | * Created by napster on 21.06.18. 21 | *

22 | * Events for the {@link space.npstr.magma.impl.connections.AudioConnection} 23 | */ 24 | public interface ConnectionEvent { 25 | } 26 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/SetEncryptionMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.conn; 18 | 19 | import space.npstr.magma.impl.EncryptionMode; 20 | 21 | /** 22 | * Created by napster on 21.06.18. 23 | */ 24 | public interface SetEncryptionMode extends ConnectionEvent { 25 | 26 | EncryptionMode getEncryptionMode(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/SetSecretKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.conn; 18 | 19 | /** 20 | * Created by napster on 22.06.18. 21 | */ 22 | public interface SetSecretKey extends ConnectionEvent { 23 | 24 | byte[] getSecretKey(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/SetSsrc.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.conn; 18 | 19 | /** 20 | * Created by napster on 21.06.18. 21 | */ 22 | public interface SetSsrc extends ConnectionEvent { 23 | 24 | int getSsrc(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/SetTargetAddress.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.conn; 18 | 19 | import java.net.InetSocketAddress; 20 | 21 | /** 22 | * Created by napster on 22.06.18. 23 | */ 24 | public interface SetTargetAddress extends ConnectionEvent { 25 | 26 | InetSocketAddress getTargetAddress(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/Shutdown.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.conn; 18 | 19 | /** 20 | * Created by napster on 21.06.18. 21 | */ 22 | public class Shutdown implements ConnectionEvent { 23 | 24 | public static final Shutdown INSTANCE = new Shutdown(); 25 | 26 | private Shutdown() {} 27 | } 28 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/UpdateSendHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.conn; 18 | 19 | import net.dv8tion.jda.api.audio.AudioSendHandler; 20 | 21 | import java.util.Optional; 22 | 23 | /** 24 | * Created by napster on 21.06.18. 25 | */ 26 | public interface UpdateSendHandler extends ConnectionEvent { 27 | 28 | Optional getAudioSendHandler(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/UpdateSpeaking.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.conn; 18 | 19 | import space.npstr.magma.api.SpeakingMode; 20 | 21 | import java.util.Set; 22 | 23 | /** 24 | * Created by napster on 21.06.18. 25 | */ 26 | public class UpdateSpeaking implements ConnectionEvent { 27 | private final boolean shouldSpeak; 28 | private final Set modes; 29 | 30 | public UpdateSpeaking(boolean shouldSpeak, Set modes) { 31 | this.shouldSpeak = shouldSpeak; 32 | this.modes = modes; 33 | } 34 | 35 | public boolean shouldSpeak() { 36 | return this.shouldSpeak; 37 | } 38 | 39 | public int getSpeakingMode() { 40 | return shouldSpeak ? SpeakingMode.toMask(modes) : 0; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/conn/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.events.audio.conn; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; 25 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/lifecycle/CloseWebSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.lifecycle; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.api.event.WebSocketClosed; 21 | import space.npstr.magma.impl.immutables.ImmutableLcEvent; 22 | 23 | /** 24 | * Created by napster on 24.04.18. 25 | */ 26 | @Value.Immutable 27 | @ImmutableLcEvent 28 | public abstract class CloseWebSocket implements LifecycleEvent { 29 | 30 | public abstract WebSocketClosed getApiEvent(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/lifecycle/ConnectWebSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.lifecycle; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.api.Member; 21 | import space.npstr.magma.impl.immutables.ImmutableLcEvent; 22 | import space.npstr.magma.impl.immutables.SessionInfo; 23 | 24 | /** 25 | * Created by napster on 24.04.18. 26 | */ 27 | @Value.Immutable 28 | @ImmutableLcEvent 29 | public abstract class ConnectWebSocket implements LifecycleEvent { 30 | 31 | @Override 32 | public Member getMember() { 33 | return this.getSessionInfo().getVoiceServerUpdate().getMember(); 34 | } 35 | 36 | public abstract SessionInfo getSessionInfo(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/lifecycle/LifecycleEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.lifecycle; 18 | 19 | import space.npstr.magma.api.Member; 20 | 21 | /** 22 | * Created by napster on 23.04.18. 23 | * 24 | * @see space.npstr.magma.impl.AudioStackLifecyclePipeline 25 | */ 26 | public interface LifecycleEvent { 27 | 28 | Member getMember(); 29 | 30 | default String getUserId() { 31 | return getMember().getUserId(); 32 | } 33 | 34 | default String getGuildId() { 35 | return getMember().getGuildId(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/lifecycle/Shutdown.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.lifecycle; 18 | 19 | import space.npstr.magma.api.Member; 20 | 21 | /** 22 | * Created by napster on 24.04.18. 23 | *

24 | * This is a lifecycle event for the whole Magma API 25 | */ 26 | public class Shutdown implements LifecycleEvent { 27 | 28 | public static final Shutdown INSTANCE = new Shutdown(); 29 | 30 | private Shutdown() { 31 | } 32 | 33 | @Override 34 | public Member getMember() { 35 | throw new UnsupportedOperationException(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/lifecycle/UpdateSendHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.lifecycle; 18 | 19 | import net.dv8tion.jda.api.audio.AudioSendHandler; 20 | import org.immutables.value.Value; 21 | import space.npstr.magma.impl.immutables.ImmutableLcEvent; 22 | 23 | import java.util.Optional; 24 | 25 | /** 26 | * Created by napster on 24.04.18. 27 | */ 28 | @Value.Immutable 29 | @ImmutableLcEvent 30 | public abstract class UpdateSendHandler implements LifecycleEvent { 31 | 32 | public abstract Optional getAudioSendHandler(); 33 | } 34 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/lifecycle/UpdateSpeakingMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Florian Spieß 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.lifecycle; 18 | 19 | import edu.umd.cs.findbugs.annotations.Nullable; 20 | import org.immutables.value.Value; 21 | import space.npstr.magma.api.SpeakingMode; 22 | import space.npstr.magma.impl.immutables.ImmutableLcEvent; 23 | 24 | import java.util.Set; 25 | 26 | @Value.Immutable 27 | @ImmutableLcEvent 28 | public abstract class UpdateSpeakingMode implements LifecycleEvent { 29 | 30 | @Nullable 31 | public abstract Set getSpeakingModes(); 32 | } 33 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/lifecycle/VoiceServerUpdate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.lifecycle; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.immutables.ImmutableLcEvent; 21 | 22 | /** 23 | * Created by napster on 22.04.18. 24 | */ 25 | @Value.Immutable 26 | @ImmutableLcEvent 27 | public abstract class VoiceServerUpdate implements LifecycleEvent { 28 | 29 | public abstract String getSessionId(); 30 | 31 | public abstract String getEndpoint(); 32 | 33 | public abstract String getToken(); 34 | } 35 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/lifecycle/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.events.audio.lifecycle; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/CloseCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws; 18 | 19 | import java.util.Optional; 20 | 21 | /** 22 | * Sources: 23 | * Flowchart 24 | * Discord Documentation 25 | */ 26 | public enum CloseCode { 27 | //@formatter:off warn resume 28 | HEARTBEAT_TIMEOUT (1000, false, true), 29 | CLOUDFLARE (1001, false, true), 30 | ABNORMAL (1006, true, true), 31 | 32 | UNKNOWN_OP_CODE (4001, true, true), 33 | NOT_AUTHENTICATED (4003, true, false), 34 | AUTHENTICATION_FAILED (4004, true, false), 35 | ALREADY_AUTHENTICATED (4005, true, false), 36 | SESSION_NO_LONGER_VALID (4006, true, false), 37 | SESSION_TIMEOUT (4009, true, false), 38 | SERVER_NOT_FOUND (4011, true, false), 39 | UNKNOWN_PROTOCOL (4012, true, false), 40 | DISCONNECTED (4014, false, false), 41 | VOICE_SERVER_CRASHED (4015, false, true), 42 | UNKNOWN_ENCRYPTION_MODE (4016, true, false), 43 | //@formatter:on 44 | ; 45 | 46 | public static Optional parse(final int code) { 47 | for (final CloseCode closeCode : CloseCode.values()) { 48 | if (closeCode.code == code) { 49 | return Optional.of(closeCode); 50 | } 51 | } 52 | return Optional.empty(); 53 | } 54 | 55 | private final int code; 56 | private final boolean shouldWarn; 57 | private final boolean shouldResume; 58 | 59 | CloseCode(final int code, final boolean shouldWarn, final boolean shouldResume) { 60 | this.code = code; 61 | this.shouldWarn = shouldWarn; 62 | this.shouldResume = shouldResume; 63 | } 64 | 65 | public int getCode() { 66 | return this.code; 67 | } 68 | 69 | public boolean shouldWarn() { 70 | return this.shouldWarn; 71 | } 72 | 73 | public boolean shouldResume() { 74 | return this.shouldResume; 75 | } 76 | 77 | 78 | @Override 79 | public String toString() { 80 | return "[" + this.code + " " + this.name() + "]"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/OpCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws; 18 | 19 | public final class OpCode { 20 | 21 | // https://discordapp.com/developers/docs/topics/opcodes-and-status-codes#voice-opcodes 22 | public static final int IDENTIFY = 0; 23 | public static final int SELECT_PROTOCOL = 1; 24 | public static final int READY = 2; 25 | public static final int HEARTBEAT = 3; 26 | public static final int SESSION_DESCRIPTION = 4; 27 | public static final int SPEAKING = 5; 28 | public static final int HEARTBEAT_ACK = 6; 29 | public static final int RESUME = 7; 30 | public static final int HELLO = 8; 31 | public static final int RESUMED = 9; 32 | public static final int OP_12 = 12; //not documented, but we do receive it 33 | public static final int CLIENT_DISCONNECT = 13; 34 | public static final int OP_14 = 14; //not documented, but we do receive it 35 | 36 | // Custom codes 37 | public static final int WEBSOCKET_CLOSE = 9001; 38 | 39 | private OpCode() {} 40 | } 41 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/Speaking.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws; 18 | 19 | import org.immutables.value.Value; 20 | import org.json.JSONObject; 21 | import space.npstr.magma.impl.events.audio.ws.in.InboundWsEvent; 22 | import space.npstr.magma.impl.events.audio.ws.out.OutboundWsEvent; 23 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 24 | 25 | /** 26 | * Created by napster on 21.04.18. 27 | */ 28 | @Value.Immutable 29 | @ImmutableWsEvent 30 | public abstract class Speaking implements InboundWsEvent, OutboundWsEvent { 31 | 32 | @Override 33 | public int getOpCode() { 34 | return OpCode.SPEAKING; 35 | } 36 | 37 | public abstract int getSpeakingMask(); 38 | 39 | public abstract int getSsrc(); 40 | 41 | @Override 42 | public Object getData() { 43 | return new JSONObject() 44 | .put("speaking", this.getSpeakingMask()) 45 | .put("delay", 0) 46 | .put("ssrc", getSsrc()) 47 | ; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/WsEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws; 18 | 19 | /** 20 | * Created by napster on 21.04.18. 21 | */ 22 | public interface WsEvent { 23 | 24 | /** 25 | * @return the Op code of the event. 26 | */ 27 | int getOpCode(); 28 | } 29 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/ClientDisconnect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.events.audio.ws.OpCode; 21 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 22 | 23 | /** 24 | * Created by napster on 21.04.18. 25 | */ 26 | @Value.Immutable 27 | @ImmutableWsEvent 28 | public abstract class ClientDisconnect implements InboundWsEvent { 29 | 30 | @Override 31 | public int getOpCode() { 32 | return OpCode.CLIENT_DISCONNECT; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/HeartbeatAck.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.events.audio.ws.OpCode; 21 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 22 | 23 | /** 24 | * Created by napster on 21.04.18. 25 | */ 26 | @Value.Immutable 27 | @ImmutableWsEvent 28 | public abstract class HeartbeatAck implements InboundWsEvent { 29 | 30 | @Override 31 | public int getOpCode() { 32 | return OpCode.HEARTBEAT_ACK; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/Hello.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.events.audio.ws.OpCode; 21 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 22 | 23 | /** 24 | * Created by napster on 20.04.18. 25 | *

26 | * Received the heartbeat interval 27 | */ 28 | @Value.Immutable 29 | @ImmutableWsEvent 30 | public abstract class Hello implements InboundWsEvent { 31 | 32 | @Override 33 | public int getOpCode() { 34 | return OpCode.HELLO; 35 | } 36 | 37 | /** 38 | * @return The heartbeat interval that we received from Discord's Hello event (op 8) 39 | */ 40 | public abstract int getHeartbeatIntervalMillis(); 41 | } 42 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/Ignored.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 21 | 22 | /** 23 | * Created by napster on 24.04.18. 24 | */ 25 | @Value.Immutable 26 | @ImmutableWsEvent 27 | public abstract class Ignored implements InboundWsEvent { 28 | 29 | @Override 30 | public abstract int getOpCode(); 31 | 32 | public abstract String getPayload(); 33 | } 34 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/InboundWsEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.json.JSONArray; 20 | import org.json.JSONObject; 21 | import space.npstr.magma.impl.EncryptionMode; 22 | import space.npstr.magma.impl.connections.AudioConnection; 23 | import space.npstr.magma.impl.events.audio.ws.OpCode; 24 | import space.npstr.magma.impl.events.audio.ws.SpeakingWsEvent; 25 | import space.npstr.magma.impl.events.audio.ws.WsEvent; 26 | import space.npstr.magma.impl.events.audio.ws.out.OutboundWsEvent; 27 | 28 | import java.util.Optional; 29 | 30 | /** 31 | * Created by napster on 20.04.18. 32 | *

33 | * Events that may be received from Discord. 34 | * Counterpart to {@link OutboundWsEvent} 35 | */ 36 | public interface InboundWsEvent extends WsEvent { 37 | 38 | /** 39 | * This method may throw if Discord sends us bogus json data. This is not unlikely given Discord's history api. todo figure out error handling for it 40 | * 41 | * @param payload 42 | * the payload of the websocket message as a string 43 | * 44 | * @return a parsed {@link InboundWsEvent} 45 | */ 46 | static InboundWsEvent from(final String payload) { 47 | final JSONObject content = new JSONObject(payload); 48 | final int opCode = content.getInt("op"); 49 | 50 | switch (opCode) { 51 | case OpCode.HELLO: 52 | final JSONObject helloD = content.getJSONObject("d"); 53 | return HelloWsEvent.builder() 54 | .heartbeatIntervalMillis(helloD.getInt("heartbeat_interval")) 55 | .build(); 56 | case OpCode.READY: 57 | final JSONObject readyD = content.getJSONObject("d"); 58 | return ReadyWsEvent.builder() 59 | .ssrc(readyD.getInt("ssrc")) 60 | .ip(readyD.getString("ip")) 61 | .port(readyD.getInt("port")) 62 | .addAllEncryptionModes(EncryptionMode.fromJson(readyD.getJSONArray("modes"))) 63 | .build(); 64 | case OpCode.SESSION_DESCRIPTION: 65 | final JSONObject sessionD = content.getJSONObject("d"); 66 | final String mode = sessionD.getString("mode"); 67 | final Optional encryptionMode = EncryptionMode.parse(mode); 68 | if (!encryptionMode.isPresent()) { 69 | throw new RuntimeException("No / unknown encryption mode: " + mode); //todo how are exceptions handled? ensure json payload is logged 70 | } 71 | final JSONArray keyArray = sessionD.getJSONArray("secret_key"); 72 | final byte[] secretKey = new byte[AudioConnection.DISCORD_SECRET_KEY_LENGTH]; 73 | for (int i = 0; i < keyArray.length(); i++) { 74 | secretKey[i] = (byte) keyArray.getInt(i); 75 | } 76 | 77 | return SessionDescriptionWsEvent.builder() 78 | .encryptionMode(encryptionMode.get()) 79 | .secretKey(secretKey) 80 | .build(); 81 | case OpCode.SPEAKING: 82 | final JSONObject speakingD = content.getJSONObject("d"); 83 | return SpeakingWsEvent.builder() 84 | .speakingMask(speakingD.getInt("speaking")) 85 | .ssrc(speakingD.getInt("ssrc")) 86 | .build(); 87 | case OpCode.HEARTBEAT_ACK: 88 | return HeartbeatAckWsEvent.builder() 89 | .build(); 90 | case OpCode.RESUMED: 91 | return ResumedWsEvent.builder() 92 | .build(); 93 | case OpCode.OP_12: 94 | case OpCode.OP_14: 95 | return IgnoredWsEvent.builder() 96 | .opCode(opCode) 97 | .payload(payload) 98 | .build(); 99 | case OpCode.CLIENT_DISCONNECT: 100 | return ClientDisconnectWsEvent.builder() 101 | .build(); 102 | case OpCode.WEBSOCKET_CLOSE: 103 | final JSONObject closedD = content.getJSONObject("d"); 104 | return WebSocketClosedWsEvent.builder() 105 | .code(closedD.getInt("code")) 106 | .reason(closedD.getString("reason")) 107 | .build(); 108 | default: 109 | return UnknownWsEvent.builder() 110 | .opCode(opCode) 111 | .payload(payload) 112 | .build(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/Ready.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.EncryptionMode; 21 | import space.npstr.magma.impl.events.audio.ws.OpCode; 22 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Created by napster on 21.04.18. 28 | */ 29 | @Value.Immutable 30 | @ImmutableWsEvent 31 | public abstract class Ready implements InboundWsEvent { 32 | 33 | @Override 34 | public int getOpCode() { 35 | return OpCode.READY; 36 | } 37 | 38 | /** 39 | * @return our ssrc 40 | */ 41 | public abstract int getSsrc(); 42 | 43 | /** 44 | * @return the ip address that we should connect our udp connection to 45 | */ 46 | public abstract String getIp(); 47 | 48 | /** 49 | * @return the udp port that we should connect our udp connection to 50 | */ 51 | public abstract int getPort(); 52 | 53 | /** 54 | * @return the supported encryption modes 55 | */ 56 | public abstract List getEncryptionModes(); 57 | } 58 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/Resumed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.events.audio.ws.OpCode; 21 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 22 | 23 | /** 24 | * Created by napster on 21.04.18. 25 | */ 26 | @Value.Immutable 27 | @ImmutableWsEvent 28 | public abstract class Resumed implements InboundWsEvent { 29 | 30 | @Override 31 | public int getOpCode() { 32 | return OpCode.RESUMED; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/SessionDescription.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.EncryptionMode; 21 | import space.npstr.magma.impl.events.audio.ws.OpCode; 22 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 23 | 24 | /** 25 | * Created by napster on 20.04.18. 26 | */ 27 | @Value.Immutable 28 | @ImmutableWsEvent 29 | public abstract class SessionDescription implements InboundWsEvent { 30 | 31 | @Override 32 | public int getOpCode() { 33 | return OpCode.SESSION_DESCRIPTION; 34 | } 35 | 36 | /** 37 | * @return the encryption mode of this session 38 | */ 39 | public abstract EncryptionMode getEncryptionMode(); 40 | 41 | /** 42 | * @return the secret key sent to us by the Session Description voice websocket event (op 4) 43 | */ 44 | public abstract byte[] getSecretKey(); 45 | } 46 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/Unknown.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 21 | 22 | /** 23 | * Created by napster on 20.04.18. 24 | *

25 | * Unknown event received 26 | */ 27 | @Value.Immutable 28 | @ImmutableWsEvent 29 | public abstract class Unknown implements InboundWsEvent { 30 | 31 | public abstract String getPayload(); 32 | } 33 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/WebSocketClosed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.in; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.events.audio.ws.OpCode; 21 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 22 | 23 | /** 24 | * Created by napster on 22.04.18. 25 | */ 26 | @Value.Immutable 27 | @ImmutableWsEvent 28 | public abstract class WebSocketClosed implements InboundWsEvent { 29 | 30 | @Override 31 | public int getOpCode() { 32 | return OpCode.WEBSOCKET_CLOSE; 33 | } 34 | 35 | public abstract int getCode(); 36 | 37 | public abstract String getReason(); 38 | } 39 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/in/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.events.audio.ws.in; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/out/Heartbeat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.out; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.events.audio.ws.OpCode; 21 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 22 | 23 | /** 24 | * Created by napster on 21.04.18. 25 | */ 26 | @Value.Immutable 27 | @ImmutableWsEvent 28 | public abstract class Heartbeat implements OutboundWsEvent { 29 | 30 | public abstract int getNonce(); 31 | 32 | @Override 33 | public int getOpCode() { 34 | return OpCode.HEARTBEAT; 35 | } 36 | 37 | @Override 38 | public Integer getData() { 39 | return this.getNonce(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/out/Identify.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.out; 18 | 19 | import org.immutables.value.Value; 20 | import org.json.JSONObject; 21 | import space.npstr.magma.impl.events.audio.ws.OpCode; 22 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 23 | 24 | /** 25 | * Created by napster on 21.04.18. 26 | */ 27 | @Value.Immutable 28 | @ImmutableWsEvent 29 | public abstract class Identify implements OutboundWsEvent { 30 | 31 | public abstract String getUserId(); 32 | 33 | public abstract String getGuildId(); 34 | 35 | public abstract String getSessionId(); 36 | 37 | public abstract String getToken(); 38 | 39 | 40 | @Override 41 | public int getOpCode() { 42 | return OpCode.IDENTIFY; 43 | } 44 | 45 | @Override 46 | public JSONObject getData() { 47 | return new JSONObject() 48 | .put("server_id", this.getGuildId()) 49 | .put("user_id", this.getUserId()) 50 | .put("session_id", this.getSessionId()) 51 | .put("token", this.getToken()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/out/OutboundWsEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.out; 18 | 19 | import org.json.JSONObject; 20 | import space.npstr.magma.impl.events.audio.ws.WsEvent; 21 | import space.npstr.magma.impl.events.audio.ws.in.InboundWsEvent; 22 | 23 | /** 24 | * Created by napster on 21.04.18. 25 | *

26 | * Payloads that we may send to Discord. 27 | * Counterpart to {@link InboundWsEvent} 28 | */ 29 | public interface OutboundWsEvent extends WsEvent { 30 | 31 | /** 32 | * @return Data payload 33 | * Should be an object that json understands and correctly parses {@literal ->} strings get double quoted for example 34 | */ 35 | Object getData(); 36 | 37 | /** 38 | * Build a message that can be send to Discord over the websocket. 39 | */ 40 | default String asMessage() { 41 | return new JSONObject() 42 | .put("op", this.getOpCode()) 43 | .put("d", this.getData()) 44 | .toString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/out/Resume.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.out; 18 | 19 | import org.immutables.value.Value; 20 | import org.json.JSONObject; 21 | import space.npstr.magma.impl.events.audio.ws.OpCode; 22 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 23 | 24 | /** 25 | * Created by napster on 25.04.18. 26 | */ 27 | @Value.Immutable 28 | @ImmutableWsEvent 29 | public abstract class Resume implements OutboundWsEvent { 30 | 31 | public abstract String getGuildId(); 32 | 33 | public abstract String getSessionId(); 34 | 35 | public abstract String getToken(); 36 | 37 | @Override 38 | public int getOpCode() { 39 | return OpCode.RESUME; 40 | } 41 | 42 | @Override 43 | public JSONObject getData() { 44 | return new JSONObject() 45 | .put("server_id", this.getGuildId()) 46 | .put("session_id", this.getSessionId()) 47 | .put("token", this.getToken()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/out/SelectProtocol.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.events.audio.ws.out; 18 | 19 | import org.immutables.value.Value; 20 | import org.json.JSONObject; 21 | import space.npstr.magma.impl.EncryptionMode; 22 | import space.npstr.magma.impl.events.audio.ws.OpCode; 23 | import space.npstr.magma.impl.immutables.ImmutableWsEvent; 24 | 25 | /** 26 | * Created by napster on 21.04.18. 27 | */ 28 | @Value.Immutable 29 | @ImmutableWsEvent 30 | public abstract class SelectProtocol implements OutboundWsEvent { 31 | 32 | public abstract String getProtocol(); 33 | 34 | public abstract String getHost(); 35 | 36 | public abstract int getPort(); 37 | 38 | public abstract EncryptionMode getEncryptionMode(); 39 | 40 | @Override 41 | public int getOpCode() { 42 | return OpCode.SELECT_PROTOCOL; 43 | } 44 | 45 | @Override 46 | public JSONObject getData() { 47 | return new JSONObject() 48 | .put("protocol", this.getProtocol()) 49 | .put("data", new JSONObject() 50 | .put("address", this.getHost()) 51 | .put("port", this.getPort()) 52 | .put("mode", this.getEncryptionMode().getKey())); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/out/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.events.audio.ws.out; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/audio/ws/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.events.audio.ws; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/events/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.events; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/immutables/ImmutableLcEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.immutables; 18 | 19 | import org.immutables.value.Value; 20 | 21 | /** 22 | * Created by napster on 21.04.18. 23 | */ 24 | @Value.Style( 25 | typeImmutable = "*LcEvent", 26 | stagedBuilder = true 27 | ) 28 | public @interface ImmutableLcEvent { 29 | } 30 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/immutables/ImmutableWsEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.immutables; 18 | 19 | import org.immutables.value.Value; 20 | 21 | /** 22 | * Created by napster on 21.04.18. 23 | */ 24 | @Value.Style( 25 | typeImmutable = "*WsEvent", 26 | stagedBuilder = true 27 | ) 28 | public @interface ImmutableWsEvent { 29 | } 30 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/immutables/SessionInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.immutables; 18 | 19 | import org.immutables.value.Value; 20 | import space.npstr.magma.impl.events.audio.lifecycle.VoiceServerUpdate; 21 | 22 | /** 23 | * Created by napster on 20.04.18. 24 | */ 25 | @Value.Immutable 26 | @Value.Style(stagedBuilder = true) 27 | public abstract class SessionInfo { 28 | 29 | public abstract VoiceServerUpdate getVoiceServerUpdate(); 30 | 31 | public String getUserId() { 32 | return this.getVoiceServerUpdate().getUserId(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/immutables/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.immutables; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/processing/PacketProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.processing; 18 | 19 | import com.iwebpp.crypto.TweetNaclFast; 20 | import edu.umd.cs.findbugs.annotations.Nullable; 21 | import net.dv8tion.jda.api.audio.AudioPacket; 22 | import net.dv8tion.jda.api.audio.AudioSendHandler; 23 | import net.dv8tion.jda.api.audio.factory.IPacketProvider; 24 | import net.dv8tion.jda.api.audio.hooks.ConnectionStatus; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import space.npstr.magma.impl.EncryptionMode; 28 | import space.npstr.magma.impl.connections.AudioConnection; 29 | 30 | import java.net.DatagramPacket; 31 | import java.net.DatagramSocket; 32 | import java.net.InetSocketAddress; 33 | import java.nio.ByteBuffer; 34 | import java.util.function.LongSupplier; 35 | 36 | /** 37 | * Created by napster on 23.06.18. 38 | */ 39 | public class PacketProvider implements IPacketProvider { 40 | 41 | private static final Logger log = LoggerFactory.getLogger(PacketProvider.class); 42 | private static final String INFORMATION_NOT_AVAILABLE = "This information is not available"; 43 | private static final ByteBuffer SILENCE_BYTES = ByteBuffer.wrap(new byte[]{(byte) 0xF8, (byte) 0xFF, (byte) 0xFE}); 44 | private static final int EMPTY_FRAMES_COUNT = 5; 45 | 46 | private final AudioConnection audioConnection; 47 | private final LongSupplier nonceSupplier; 48 | private ByteBuffer packetBuffer = ByteBuffer.allocate(512); //packets usually take up about 400-500 bytes 49 | private final byte[] nonceBuffer = new byte[TweetNaclFast.SecretBox.nonceLength]; 50 | 51 | private char seq = 0; //Sequence of audio packets. Used to determine the order of the packets. 52 | private int timestamp = 0; //Used to sync up our packets within the same timeframe of other people talking. 53 | 54 | // opus interpolation handling 55 | // https://discordapp.com/developers/docs/topics/voice-connections#voice-data-interpolation 56 | private int sendSilentFrames = EMPTY_FRAMES_COUNT; 57 | 58 | public PacketProvider(final AudioConnection audioConnection, final LongSupplier nonceSupplier) { 59 | this.audioConnection = audioConnection; 60 | this.nonceSupplier = nonceSupplier; 61 | } 62 | 63 | @Override 64 | public String getIdentifier() { 65 | return ""; 66 | } 67 | 68 | @Override 69 | public String getConnectedChannel() { 70 | throw new UnsupportedOperationException(INFORMATION_NOT_AVAILABLE); 71 | } 72 | 73 | @Override 74 | public DatagramSocket getUdpSocket() { 75 | return this.audioConnection.getUdpSocket(); 76 | } 77 | 78 | @Nullable 79 | @Override 80 | public InetSocketAddress getSocketAddress() { 81 | return this.audioConnection.getUdpTargetAddress(); 82 | } 83 | 84 | @Override 85 | public void onConnectionError(final ConnectionStatus status) { 86 | // this is not for UDP you smartass 87 | throw new UnsupportedOperationException("Connection error, on a udp connection...that's not a real thing."); 88 | } 89 | 90 | @Override 91 | public void onConnectionLost() { 92 | // this is not for UDP you smartass 93 | throw new UnsupportedOperationException("Connection lost, on a udp connection...that's not a real thing."); 94 | } 95 | 96 | //realistically, this is the only thing that is ever called by the NativeAudioSystem 97 | @Nullable 98 | @Override 99 | public DatagramPacket getNextPacket(final boolean changeTalking) { 100 | final InetSocketAddress targetAddress = this.audioConnection.getUdpTargetAddress(); 101 | if (targetAddress == null) { 102 | return null; 103 | } 104 | final ByteBuffer nextPacket = getNextPacketRaw(changeTalking); 105 | return nextPacket == null ? null : asDatagramPacket(nextPacket, targetAddress); 106 | } 107 | 108 | @Nullable 109 | @Override 110 | public ByteBuffer getNextPacketRaw(final boolean changeTalking) { 111 | try { 112 | return this.buildNextPacket(changeTalking); 113 | } catch (final Exception e) { 114 | log.error("Failed to get next packet", e); 115 | return null; 116 | } 117 | } 118 | 119 | @Nullable 120 | @SuppressWarnings("squid:S3776") 121 | private ByteBuffer buildNextPacket(final boolean changeTalking) { 122 | 123 | final EncryptionMode encryptionMode = this.audioConnection.getEncryptionMode(); 124 | final byte[] secretKey = this.audioConnection.getSecretKey(); 125 | final Integer ssrc = this.audioConnection.getSsrc(); 126 | final AudioSendHandler sendHandler = this.audioConnection.getSendHandler(); 127 | 128 | //preconditions fulfilled? 129 | if (encryptionMode == null 130 | || secretKey == null 131 | || ssrc == null 132 | || sendHandler == null 133 | || !sendHandler.canProvide()) { 134 | if (this.audioConnection.isSpeaking() && changeTalking) { 135 | this.audioConnection.updateSpeaking(false); 136 | } 137 | this.sendSilentFrames = EMPTY_FRAMES_COUNT; 138 | return null; 139 | } 140 | 141 | final AudioPacket nextAudioPacket; 142 | if (this.sendSilentFrames <= 0) { 143 | //audio data provided? 144 | final ByteBuffer rawAudio = sendHandler.provide20MsAudio(); 145 | if (rawAudio == null || !rawAudio.hasRemaining()) { 146 | if (this.audioConnection.isSpeaking() && changeTalking) { 147 | this.audioConnection.updateSpeaking(false); 148 | } 149 | this.sendSilentFrames = EMPTY_FRAMES_COUNT; 150 | return null; 151 | } 152 | if (!rawAudio.hasArray()) { 153 | // we can't use the boxer without an array so encryption would not work 154 | log.error("AudioSendHandler provided ByteBuffer without a backing array! This is unsupported."); 155 | return null; 156 | } 157 | nextAudioPacket = new AudioPacket(this.seq, this.timestamp, ssrc, rawAudio); 158 | } else { 159 | nextAudioPacket = new AudioPacket(this.seq, this.timestamp, ssrc, SILENCE_BYTES); 160 | this.sendSilentFrames--; 161 | log.trace("Sending silent frame, silent frames left {}", this.sendSilentFrames); 162 | } 163 | 164 | final ByteBuffer nextPacket = this.packetBuffer = PacketUtil.encryptPacket(nextAudioPacket, this.packetBuffer, 165 | encryptionMode, secretKey, this.nonceSupplier, this.nonceBuffer); 166 | 167 | if (!this.audioConnection.isSpeaking()) { 168 | this.audioConnection.updateSpeaking(true); 169 | } 170 | 171 | if (this.seq + 1 > Character.MAX_VALUE) { 172 | this.seq = 0; 173 | } else { 174 | this.seq++; 175 | } 176 | 177 | this.timestamp += AudioConnection.OPUS_FRAME_SIZE; 178 | 179 | return nextPacket; 180 | } 181 | 182 | private DatagramPacket asDatagramPacket(final ByteBuffer buffer, final InetSocketAddress targetAddress) { 183 | final byte[] data = buffer.array(); 184 | final int offset = buffer.arrayOffset(); 185 | final int limit = buffer.remaining(); 186 | return new DatagramPacket(data, offset, limit, targetAddress); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/processing/PacketUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl.processing; 18 | 19 | import com.iwebpp.crypto.TweetNaclFast; 20 | import net.dv8tion.jda.api.audio.AudioPacket; 21 | import space.npstr.magma.impl.EncryptionMode; 22 | 23 | import java.nio.ByteBuffer; 24 | import java.util.concurrent.ThreadLocalRandom; 25 | import java.util.function.LongSupplier; 26 | 27 | /** 28 | * Created by napster on 23.06.18. 29 | */ 30 | public class PacketUtil { 31 | 32 | private PacketUtil() { 33 | } 34 | 35 | //this may reallocate the passed ByteBuffer if it is too small 36 | public static ByteBuffer encryptPacket(final AudioPacket audioPacket, final ByteBuffer packetBuffer, 37 | final EncryptionMode encryptionMode, final byte[] secretKey, 38 | final LongSupplier nonceSupplier, final byte[] nonceBuffer) { 39 | 40 | final int nonceLength; 41 | switch (encryptionMode) { 42 | case XSALSA20_POLY1305: 43 | nonceLength = 0; 44 | break; 45 | case XSALSA20_POLY1305_LITE: 46 | writeNonce(nonceSupplier.getAsLong(), nonceBuffer); 47 | nonceLength = 4; 48 | break; 49 | case XSALSA20_POLY1305_SUFFIX: 50 | ThreadLocalRandom.current().nextBytes(nonceBuffer); 51 | nonceLength = TweetNaclFast.SecretBox.nonceLength; 52 | break; 53 | default: 54 | throw new IllegalStateException("Encryption mode [" + encryptionMode + "] is not supported!"); 55 | } 56 | 57 | return audioPacket.asEncryptedPacket(packetBuffer, secretKey, nonceBuffer, nonceLength); 58 | } 59 | 60 | //@formatter:off 61 | public static void writeNonce(final long nonce, final byte[] nonceBuffer) { 62 | nonceBuffer[0] = (byte) ((nonce >>> 24) & 0xFF); 63 | nonceBuffer[1] = (byte) ((nonce >>> 16) & 0xFF); 64 | nonceBuffer[2] = (byte) ((nonce >>> 8) & 0xFF); 65 | nonceBuffer[3] = (byte) ( nonce & 0xFF); 66 | } 67 | //@formatter:on 68 | } 69 | -------------------------------------------------------------------------------- /impl/src/main/java/space/npstr/magma/impl/processing/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @FieldsAreNonNullByDefault 18 | @ParametersAreNonnullByDefault 19 | @ReturnTypesAreNonNullByDefault 20 | package space.npstr.magma.impl.processing; 21 | 22 | import space.npstr.annotations.FieldsAreNonNullByDefault; 23 | import space.npstr.annotations.ParametersAreNonnullByDefault; 24 | import space.npstr.annotations.ReturnTypesAreNonNullByDefault; 25 | -------------------------------------------------------------------------------- /impl/src/test/java/space/npstr/magma/impl/EncryptionModeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma.impl; 18 | 19 | import org.json.JSONArray; 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.util.Collections; 23 | import java.util.List; 24 | import java.util.Optional; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertFalse; 28 | import static org.junit.jupiter.api.Assertions.assertTrue; 29 | 30 | /** 31 | * Created by napster on 07.05.18. 32 | */ 33 | public class EncryptionModeTest { 34 | 35 | @Test 36 | public void testPreference() { 37 | JSONArray array = new JSONArray(); 38 | array.put("xsalsa20_poly1305_lite"); 39 | array.put("xsalsa20_poly1305"); 40 | array.put("xsalsa20_poly1305_suffix"); 41 | final List allModes = EncryptionMode.fromJson(array); 42 | assertEquals(allModes.size(), array.length(), "all known modes were parsed"); 43 | 44 | final Optional preferredMode = EncryptionMode.getPreferredMode(allModes); 45 | assertTrue(preferredMode.isPresent(), "return a preferred mode"); 46 | assertEquals(EncryptionMode.XSALSA20_POLY1305_LITE, preferredMode.get(), "prefer lite over all others"); 47 | 48 | 49 | final List empty = Collections.emptyList(); 50 | assertFalse(EncryptionMode.getPreferredMode(empty).isPresent(), "empty list returns empty optional"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk8 3 | -------------------------------------------------------------------------------- /platform/.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /platform/gradle/dependency-locks/archives.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /platform/gradle/dependency-locks/classpath.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE 5 | -------------------------------------------------------------------------------- /platform/gradle/dependency-locks/default.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | -------------------------------------------------------------------------------- /platform/gradle/dependency-locks/jacocoAgent.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.jacoco:org.jacoco.agent:0.8.5 5 | -------------------------------------------------------------------------------- /platform/gradle/dependency-locks/jacocoAnt.lockfile: -------------------------------------------------------------------------------- 1 | # This is a Gradle generated file for dependency locking. 2 | # Manual edits can break the build and are not advised. 3 | # This file is expected to be part of source control. 4 | org.jacoco:org.jacoco.agent:0.8.5 5 | org.jacoco:org.jacoco.ant:0.8.5 6 | org.jacoco:org.jacoco.core:0.8.5 7 | org.jacoco:org.jacoco.report:0.8.5 8 | org.ow2.asm:asm-analysis:7.2 9 | org.ow2.asm:asm-commons:7.2 10 | org.ow2.asm:asm-tree:7.2 11 | org.ow2.asm:asm:7.2 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'magma' 2 | 3 | include 'api' 4 | include 'impl' 5 | include 'platform' 6 | -------------------------------------------------------------------------------- /src/main/java/space/npstr/magma/MagmaFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 Dennis Neufeld 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package space.npstr.magma; 18 | 19 | import java.util.function.Function; 20 | import net.dv8tion.jda.api.audio.factory.IAudioSendFactory; 21 | import space.npstr.magma.api.MagmaApi; 22 | import space.npstr.magma.api.Member; 23 | import space.npstr.magma.impl.Magma; 24 | 25 | /** 26 | * Created by napster on 08.05.19. 27 | */ 28 | public class MagmaFactory { 29 | 30 | /** 31 | * Create a new Magma instance. More than one of these is not necessary, even if you are managing several shards and 32 | * several bot accounts. A single instance of this scales automatically according to your needs and hardware. 33 | * 34 | * @param sendFactoryProvider a provider of {@link IAudioSendFactory}s. It will have members applied to it. 35 | */ 36 | public static MagmaApi of(final Function sendFactoryProvider) { 37 | return new Magma(sendFactoryProvider); 38 | } 39 | 40 | private MagmaFactory() {} 41 | } 42 | --------------------------------------------------------------------------------