Compare commits
	
		
			213 Commits
		
	
	
		
			fff20c8dab
			...
			feat/messa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 272225ac07 | |||
| 42a675dc96 | |||
| eea1597b15 | |||
| 83b446e6fb | |||
| ef9384336a | |||
| ebf5600e29 | |||
| 070319853a | |||
| 60ead419e2 | |||
| 0b7b607a23 | |||
| 991d1c047b | |||
| ec17952375 | |||
| 0114cffcbd | |||
| 450f1fdaa1 | |||
| 92540576df | |||
| 8a3cde52fd | |||
| 8495659364 | |||
| 0973016a74 | |||
| 0590789359 | |||
| 2647a1f0b4 | |||
| a3c5eb62ac | |||
| 996f854ff7 | |||
| d2eec8b07c | |||
| caa2ee7089 | |||
| 2e5979e6e4 | |||
| ab1eb74e85 | |||
| a5737b9eaa | |||
| faca21724c | |||
| 4c5e28b679 | |||
| 33f355e6ea | |||
| d32ac62b4a | |||
| e7c35d9308 | |||
| d4a91f3251 | |||
| bce4ce7812 | |||
| bae077654e | |||
| 203498de68 | |||
| 24df1731da | |||
| e3fc10a1ce | |||
| 78cacb7eb6 | |||
| 7287d44645 | |||
| 630c8ddee5 | |||
| 4e27e6ce56 | |||
| 432cf78a2e | |||
| f940f48566 | |||
| f4ee258eb1 | |||
| 56b668851b | |||
| 823402e0cd | |||
| 91b1df8d5b | |||
| 76d7e9ef4f | |||
| 236c584da8 | |||
| f6db113deb | |||
| 9058629af5 | |||
| 8f14b0feb9 | |||
| 3e02e7b85b | |||
| ba560cb909 | |||
| 6cdca411c6 | |||
| 3a39b268c1 | |||
| ca0ab13316 | |||
| ded81983e3 | |||
| 92704ca230 | |||
| 5d8f737481 | |||
| 8943d6d4a4 | |||
| 842fcfe5ac | |||
| 858fab5e32 | |||
| 20471fefea | |||
| 8d27ec28db | |||
| f81602f5db | |||
| dbad157058 | |||
| ad19ed6ada | |||
| ca822909e3 | |||
| caeaec1926 | |||
| f4dbdc302d | |||
| fd87431d51 | |||
| 3b65784770 | |||
| b4af922ac2 | |||
| 1146fa1cc6 | |||
| 9d88ca902d | |||
| e9b5610f3c | |||
| 8e872e7ada | |||
| 43c5946227 | |||
| 71c910ab93 | |||
| c5a7f3665e | |||
| 3baf1eda9a | |||
| 628abb4239 | |||
| 9a89544a9e | |||
| 455fc4955f | |||
| b72c55fc6e | |||
| 3691434f66 | |||
| 74d55d81ca | |||
| 404f221ccf | |||
| 7249c99b69 | |||
| b764972eba | |||
| 83ee4b1efa | |||
| 6b86e9ff87 | |||
| 7a40aebd6d | |||
| 76ab779ab2 | |||
| 3d19fae6b8 | |||
| 1ecbc563a6 | |||
| bf0022775d | |||
| a17f5e826f | |||
| 3de0f32074 | |||
| aaf4f3297a | |||
| 445190db89 | |||
| 9d3a6b8b85 | |||
| 1cb6cd0e05 | |||
| a5e1230fc6 | |||
| 432db43bf5 | |||
| 7a2c15d877 | |||
| f32c7045a1 | |||
| 69b27e4554 | |||
| d8504a7cde | |||
| bef34ee548 | |||
| 29bb7e667e | |||
| 94da2a0e3c | |||
| 7f47130461 | |||
| 83a3efd4b8 | |||
| aefd8a51a0 | |||
| 4332592dfa | |||
| c363ab9744 | |||
| 33392b02fb | |||
| b7333c2e5e | |||
| 239e921e6f | |||
| 6eb7fb723f | |||
| 659218682e | |||
| 019ba8f552 | |||
| f89ae5e425 | |||
| 582e0f0bae | |||
| 0ea330d48b | |||
| c241f6b1fe | |||
| 4ed5482b63 | |||
| 4d20dd65f2 | |||
| 2ab30a2bc1 | |||
| 420408fbdf | |||
| df10c54767 | |||
| 18d85edcf2 | |||
| b371ea8520 | |||
| b28406b16b | |||
| f31292c5b7 | |||
| de83137886 | |||
| fd3933df25 | |||
| c00b6033cc | |||
| 186fa10684 | |||
| 7d378e6451 | |||
| f835cfa500 | |||
| cb8b799000 | |||
| fcc99597e6 | |||
| d457e57614 | |||
| 8a7348eebc | |||
| 480c1ec81f | |||
| 3d2bca4548 | |||
| 5689fef866 | |||
| 7d9e14eb25 | |||
| 7fa402ac38 | |||
| eaff502892 | |||
| 76d4d5b525 | |||
| 0ce0b3eb4f | |||
| 11fd16fa77 | |||
| bbeb58e5e4 | |||
| e39ac2d67e | |||
| 0f337696c1 | |||
| 9f87f47b1f | |||
| 9d81522429 | |||
| 7888819f6e | |||
| 3d5201329b | |||
| 4b835187b5 | |||
| 3778f45cf3 | |||
| 6bd6021b86 | |||
| b0abb423d3 | |||
| 22f9810f7b | |||
| 9768572577 | |||
| 2cdf574df0 | |||
| 0c3aeed4f4 | |||
| 0615741def | |||
| 1aa052a85b | |||
| 19bf9d24bc | |||
| ae7e66d06d | |||
| df94601816 | |||
| d86c307166 | |||
| f4a6bf937d | |||
| 11ebb3fdea | |||
| fd749b31d8 | |||
| cf45d0e8ea | |||
| 4a13899975 | |||
| 0c9d8ecb53 | |||
| ec75215b72 | |||
| f12c83cf96 | |||
| d0cbb096a8 | |||
| 824fcca52e | |||
| 492399ec82 | |||
| 3e50a065a3 | |||
| b562349cae | |||
| e38a382a7a | |||
| b0742e65ad | |||
| 410b299ad6 | |||
| 89d172e2f3 | |||
| b809411faf | |||
| 42a1d86bf9 | |||
| 1a4dfff8ec | |||
| bde3c401fb | |||
| 533af43bea | |||
| ac8f6bdbb3 | |||
| f011ec513a | |||
| 7901e8c380 | |||
| 7fb27795d9 | |||
| 7a4bfbb6f8 | |||
| 03f628aa03 | |||
| e57f29500b | |||
| ddeed7c0dc | |||
| 78e3a0ad51 | |||
| 421fb230e9 | |||
| 3db51e9c0b | |||
| aaa2785d37 | |||
| 14f9448ba4 | |||
| 55bfeee2d0 | 
							
								
								
									
										17
									
								
								.gitea/workflows/github-mirror.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.gitea/workflows/github-mirror.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | name: github-mirror | ||||||
|  | on: push | ||||||
|  | jobs: | ||||||
|  |   push-github: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout repository | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 0 | ||||||
|  |       - name: Push to GitHub | ||||||
|  |         uses: pixta-dev/repository-mirroring-action@v1 | ||||||
|  |         with: | ||||||
|  |           target_repo_url: | ||||||
|  |             git@github.com:EkiciLP/Marinara.git | ||||||
|  |           ssh_private_key: | ||||||
|  |             ${{ secrets.MIRROR_SSH_PRIVATE_KEY }} | ||||||
							
								
								
									
										50
									
								
								.gitea/workflows/gradle-build.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								.gitea/workflows/gradle-build.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | name: Build | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   Gradle-Build: | ||||||
|  |  | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|  |     steps: | ||||||
|  |     - name: Checkout code | ||||||
|  |       uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |     - name: Set up JDK | ||||||
|  |       uses: actions/setup-java@v4 | ||||||
|  |       with: | ||||||
|  |         java-version: '23' | ||||||
|  |         check-latest: true | ||||||
|  |         distribution: 'zulu' | ||||||
|  |  | ||||||
|  |     - name: Setup Gradle | ||||||
|  |       uses: gradle/actions/setup-gradle@v4 | ||||||
|  |       with: | ||||||
|  |         add-job-summary: always | ||||||
|  |         cache-cleanup: on-success | ||||||
|  |  | ||||||
|  |     - name: Cache Gradle dependencies | ||||||
|  |       uses: actions/cache@v4 | ||||||
|  |       with: | ||||||
|  |         path: |  | ||||||
|  |           ~/.gradle/caches | ||||||
|  |         key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||||||
|  |         restore-keys: | | ||||||
|  |           ${{ runner.os }}-gradle- | ||||||
|  |  | ||||||
|  |     - name: Build | ||||||
|  |       run: chmod +x gradlew; ./gradlew assemble | ||||||
|  |  | ||||||
|  |     - name: Move artifacts | ||||||
|  |       run: | | ||||||
|  |         mkdir artifacts; mv lib/build/libs/*.jar artifacts;  | ||||||
|  |         mv wrapper/javacord/build/libs/*.jar artifacts;  | ||||||
|  |         mv wrapper/discord4j/build/libs/*.jar artifacts; | ||||||
|  |  | ||||||
|  |     - name: Upload artifact | ||||||
|  |       uses: christopherhx/gitea-upload-artifact@v4 | ||||||
|  |       with: | ||||||
|  |         name: artifacts | ||||||
|  |         path: artifacts | ||||||
							
								
								
									
										40
									
								
								.gitea/workflows/gradle-publish-dev.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.gitea/workflows/gradle-publish-dev.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | name: Publish | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: [dev] | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   Gradle-Publish: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout code | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - name: Set up JDK | ||||||
|  |         uses: actions/setup-java@v4 | ||||||
|  |         with: | ||||||
|  |           java-version: '23' | ||||||
|  |           check-latest: true | ||||||
|  |           distribution: 'zulu' | ||||||
|  |  | ||||||
|  |       - name: Setup Gradle | ||||||
|  |         uses: gradle/actions/setup-gradle@v4 | ||||||
|  |         with: | ||||||
|  |           add-job-summary: always | ||||||
|  |           cache-cleanup: on-success | ||||||
|  |  | ||||||
|  |       - name: Cache Gradle dependencies | ||||||
|  |         uses: actions/cache@v4 | ||||||
|  |         with: | ||||||
|  |           path: |  | ||||||
|  |             ~/.gradle/caches | ||||||
|  |           key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             ${{ runner.os }}-gradle- | ||||||
|  |  | ||||||
|  |       - name: Publish Dev | ||||||
|  |         env: | ||||||
|  |           GITEA_TOKEN: ${{ secrets.PUBLISH_PACKAGE_TOKEN }} | ||||||
|  |         run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository  | ||||||
							
								
								
									
										40
									
								
								.gitea/workflows/gradle-publish.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.gitea/workflows/gradle-publish.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | name: Publish | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: [master] | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   Gradle-Publish: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout code | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - name: Set up JDK | ||||||
|  |         uses: actions/setup-java@v4 | ||||||
|  |         with: | ||||||
|  |           java-version: '23' | ||||||
|  |           check-latest: true | ||||||
|  |           distribution: 'zulu' | ||||||
|  |  | ||||||
|  |       - name: Setup Gradle | ||||||
|  |         uses: gradle/actions/setup-gradle@v4 | ||||||
|  |         with: | ||||||
|  |           add-job-summary: always | ||||||
|  |           cache-cleanup: on-success | ||||||
|  |  | ||||||
|  |       - name: Cache Gradle dependencies | ||||||
|  |         uses: actions/cache@v4 | ||||||
|  |         with: | ||||||
|  |           path: |  | ||||||
|  |             ~/.gradle/caches | ||||||
|  |           key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             ${{ runner.os }}-gradle- | ||||||
|  |  | ||||||
|  |       - name: Publish Release | ||||||
|  |         env: | ||||||
|  |           GITEA_TOKEN: ${{ secrets.PUBLISH_PACKAGE_TOKEN }} | ||||||
|  |         run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository -Prelease | ||||||
							
								
								
									
										54
									
								
								.gitea/workflows/gradle-test.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								.gitea/workflows/gradle-test.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | name: Test | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   Gradle-Test: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout code | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - name: Set up JDK | ||||||
|  |         uses: actions/setup-java@v4 | ||||||
|  |         with: | ||||||
|  |           java-version: '23' | ||||||
|  |           check-latest: true | ||||||
|  |           distribution: 'zulu' | ||||||
|  |  | ||||||
|  |       - name: Setup Gradle | ||||||
|  |         uses: gradle/actions/setup-gradle@v4 | ||||||
|  |         with: | ||||||
|  |           add-job-summary: always | ||||||
|  |           cache-cleanup: on-success | ||||||
|  |  | ||||||
|  |       - name: Cache Gradle dependencies | ||||||
|  |         uses: actions/cache@v4 | ||||||
|  |         with: | ||||||
|  |           path: |  | ||||||
|  |             ~/.gradle/caches | ||||||
|  |           key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             ${{ runner.os }}-gradle- | ||||||
|  |  | ||||||
|  |       - name: Test | ||||||
|  |         env: | ||||||
|  |           DISCORD_TEST_TOKEN: ${{ secrets.DISCORD_TOKEN }} | ||||||
|  |         run: chmod +x gradlew; ./gradlew test | ||||||
|  |  | ||||||
|  |       - name: Move Test Results | ||||||
|  |         if: always() | ||||||
|  |         run: | | ||||||
|  |           mkdir test-results/; | ||||||
|  |           [ -d lib/build/test-results/test/ ] && mv lib/build/test-results/test/*.xml test-results/; | ||||||
|  |           [ -d wrapper/javacord/build/test-results/test/ ] && mv wrapper/javacord/build/test-results/test/*.xml test-results/; | ||||||
|  |           [ -d wrapper/discord4j/build/test-results/test/ ] && mv wrapper/discord4j/build/test-results/test/*.xml test-results/; | ||||||
|  |  | ||||||
|  |       - name: Upload Test Result | ||||||
|  |         uses: christopherhx/gitea-upload-artifact@v4 | ||||||
|  |         if: always() | ||||||
|  |         with: | ||||||
|  |           name: test-results | ||||||
|  |           path: test-results/ | ||||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -4,3 +4,6 @@ | |||||||
| # Ignore Gradle build output directory | # Ignore Gradle build output directory | ||||||
| build | build | ||||||
| .vscode | .vscode | ||||||
|  | lib/bin | ||||||
|  | wrapper/javacord/bin | ||||||
|  | wrapper/discord4j/bin | ||||||
|   | |||||||
							
								
								
									
										65
									
								
								build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | import java.io.ByteArrayOutputStream | ||||||
|  |  | ||||||
|  | plugins { | ||||||
|  |     `maven-publish` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | allprojects { | ||||||
|  |     group = "net.tomatentum.Marinara" | ||||||
|  |     version = "1.0.0-RC1" + (if (!project.hasProperty("release")) ("-" + getGitHash()) else "") | ||||||
|  |     description = "A simple but powerful, library-agnostic Discord Interaction Wrapper." | ||||||
|  |     plugins.withType<JavaPlugin> { | ||||||
|  |         tasks.withType<Jar>().configureEach { | ||||||
|  |             archiveBaseName.set("marinara-" + archiveBaseName.get()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     repositories { | ||||||
|  |         // Use Maven Central for resolving dependencies. | ||||||
|  |         mavenCentral() | ||||||
|  |         maven { | ||||||
|  |             url = uri("https://git.tomatentum.net/api/packages/tueem/maven") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | subprojects { | ||||||
|  |     apply(plugin = "maven-publish") | ||||||
|  |     apply(plugin = "java-library") | ||||||
|  |  | ||||||
|  |     plugins.withType<MavenPublishPlugin> { | ||||||
|  |         publishing { | ||||||
|  |             publications { | ||||||
|  |                 create<MavenPublication>("maven") { | ||||||
|  |                     if (!project.hasProperty("release")) | ||||||
|  |                         artifactId = project.getName() + "-dev" | ||||||
|  |                     from(components["java"]) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             repositories { | ||||||
|  |                 maven { | ||||||
|  |                     name = "Gitea" | ||||||
|  |                     url = uri("https://git.tomatentum.net/api/packages/tueem/maven/") | ||||||
|  |  | ||||||
|  |                     credentials(HttpHeaderCredentials::class) { | ||||||
|  |                         name = "Authorization" | ||||||
|  |                         value = "token " + System.getenv("GITEA_TOKEN") | ||||||
|  |                     } | ||||||
|  |                     authentication { | ||||||
|  |                         create<HttpHeaderAuthentication>("header") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun getGitHash(): String { | ||||||
|  |     val output = ByteArrayOutputStream() | ||||||
|  |     project.exec { | ||||||
|  |         commandLine("git", "rev-parse", "--short", "HEAD") | ||||||
|  |         standardOutput = output | ||||||
|  |     } | ||||||
|  |     return output.toString().trim() | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								gradle.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								gradle.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | org.gradle.caching=true | ||||||
| @@ -2,13 +2,20 @@ | |||||||
| # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format | # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format | ||||||
|  |  | ||||||
| [versions] | [versions] | ||||||
| commons-math3 = "3.6.1" |  | ||||||
| guava = "33.0.0-jre" |  | ||||||
| junit-jupiter = "5.10.2" | junit-jupiter = "5.10.2" | ||||||
| log4j = "2.24.1" | slf4j = "2.0.17" | ||||||
|  | javacord = "3.8.0" | ||||||
|  | discord4j = "3.2.7" | ||||||
|  | geantyref = "2.0.0" | ||||||
|  | mockito = "5.15.2" | ||||||
|  | cutin = "0.2.0" | ||||||
|  |  | ||||||
| [libraries] | [libraries] | ||||||
| commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } |  | ||||||
| guava = { module = "com.google.guava:guava", version.ref = "guava" } |  | ||||||
| junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } | junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } | ||||||
| log4j = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j"} | slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j"} | ||||||
|  | slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j"} | ||||||
|  | javacord = { module = "org.javacord:javacord", version.ref = "javacord"} | ||||||
|  | discord4j = { module = "com.discord4j:discord4j-core", version.ref = "discord4j"} | ||||||
|  | geantyref = { module = "io.leangen.geantyref:geantyref", version.ref = "geantyref"} | ||||||
|  | mockito = {module = "org.mockito:mockito-core", version.ref = "mockito"} | ||||||
|  | cutin = {module = "net.tomatentum.cutin:lib", version.ref = "cutin"} | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip | ||||||
| networkTimeout=10000 | networkTimeout=10000 | ||||||
| validateDistributionUrl=true | validateDistributionUrl=true | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
|   | |||||||
| @@ -10,30 +10,20 @@ plugins { | |||||||
|     `java-library` |     `java-library` | ||||||
| } | } | ||||||
|  |  | ||||||
| repositories { |  | ||||||
|     // Use Maven Central for resolving dependencies. |  | ||||||
|     mavenCentral() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     // Use JUnit Jupiter for testing. |     // Use JUnit Jupiter for testing. | ||||||
|     testImplementation(libs.junit.jupiter) |     testImplementation(libs.junit.jupiter) | ||||||
|  |  | ||||||
|     testRuntimeOnly("org.junit.platform:junit-platform-launcher") |     testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||||||
|  |     implementation(libs.slf4j) | ||||||
|     // This dependency is exported to consumers, that is to say found on their compile classpath. |     implementation(libs.geantyref) | ||||||
|     api(libs.commons.math3) |     api(libs.cutin) | ||||||
|  |  | ||||||
|     // This dependency is used internally, and not exposed to consumers on their own compile classpath. |  | ||||||
|     implementation(libs.guava) |  | ||||||
|     implementation(libs.log4j) |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Apply a specific Java toolchain to ease working on different environments. | // Apply a specific Java toolchain to ease working on different environments. | ||||||
| java { | java { | ||||||
|     toolchain { |     toolchain { | ||||||
|         languageVersion = JavaLanguageVersion.of(21) |         languageVersion = JavaLanguageVersion.of(23) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,85 @@ | |||||||
| package net.tomatentum.marinara; | package net.tomatentum.marinara; | ||||||
|  |  | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodExecutor; | ||||||
|  | import net.tomatentum.cutin.ProcessorMethodExecutor; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||||
|  | import net.tomatentum.marinara.container.InteractionCheckContainer; | ||||||
|  | import net.tomatentum.marinara.container.InteractionMethodContainer; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.processor.AutocompleteInteractionProcessor; | ||||||
|  | import net.tomatentum.marinara.interaction.processor.DirectInteractionProcessor; | ||||||
|  | import net.tomatentum.marinara.util.ObjectAggregator; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  |  | ||||||
| public class Marinara { | public class Marinara { | ||||||
|     public static Marinara load() { |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |      | ||||||
|  |     public static Marinara load(LibraryWrapper wrapper) { | ||||||
|  |         return new Marinara(wrapper); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private LibraryWrapper wrapper; | ||||||
|  |     private MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer; | ||||||
|  |     private MethodContainer<InteractionIdentifier, Object> interactionContainer; | ||||||
|  |     private MethodExecutor<Object> interactionExecutor; | ||||||
|  |  | ||||||
|  |     private Marinara(LibraryWrapper wrapper) { | ||||||
|  |         this.wrapper = wrapper; | ||||||
|  |         this.checkContainer = new InteractionCheckContainer(); | ||||||
|  |         this.interactionContainer = new InteractionMethodContainer(getCheckContainer(), getWrapper().getContextObjectProvider()); | ||||||
|  |         IdentifierProvider provider = wrapper.createIdentifierProvider(); | ||||||
|  |         ProcessorMethodExecutor<InteractionIdentifier, Object> exec = new ProcessorMethodExecutor<>(getInteractionContainer()); | ||||||
|  |         exec | ||||||
|  |             .addProcessor(new DirectInteractionProcessor(provider, InteractionType.COMMAND, InteractionType.BUTTON)) | ||||||
|  |             .addProcessor(new AutocompleteInteractionProcessor(getWrapper(), provider)); | ||||||
|  |         this.interactionExecutor = exec; | ||||||
|  |         wrapper.subscribeInteractions(this.interactionExecutor::handle); | ||||||
|  |         logger.info("Marinara loaded successfully!"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     //TODO move to future interactionstructure module | ||||||
|  |     public void registerCommands() { | ||||||
|  |         List<InteractionIdentifier> slashIdentifiers = getInteractionContainer().identifiers().stream() | ||||||
|  |             .filter(i -> i.type().equals(InteractionType.COMMAND)) | ||||||
|  |             .toList(); | ||||||
|  |  | ||||||
|  |         SlashCommandDefinition[] defs = new ObjectAggregator<InteractionIdentifier, RootCommandIdentifier, SlashCommandDefinition>( | ||||||
|  |             i -> Arrays.asList((RootCommandIdentifier)i.rootNode()), | ||||||
|  |             SlashCommandDefinition::addIdentifier, | ||||||
|  |             SlashCommandDefinition::new) | ||||||
|  |             .aggregate(slashIdentifiers) | ||||||
|  |             .toArray(SlashCommandDefinition[]::new); | ||||||
|  |  | ||||||
|  |         wrapper.getRegisterer().register(defs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public LibraryWrapper getWrapper() { | ||||||
|  |         return this.wrapper; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public MethodContainer<InteractionIdentifier, Object> getInteractionContainer() { | ||||||
|  |         return this.interactionContainer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public MethodContainer<CheckMethodIdentifier, CheckExecutionContext> getCheckContainer() { | ||||||
|  |         return this.checkContainer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public MethodExecutor<Object> getInteractionExecutor() { | ||||||
|  |         return interactionExecutor; | ||||||
|  |     } | ||||||
|      |      | ||||||
| } | } | ||||||
| } |  | ||||||
|   | |||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | package net.tomatentum.marinara.checks; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.Annotation; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.method.ReflectedMethod; | ||||||
|  |  | ||||||
|  | public record AppliedCheck( | ||||||
|  |         Annotation annotation,  | ||||||
|  |         ReflectedMethod<CheckMethodIdentifier, CheckExecutionContext> preExec, | ||||||
|  |         ReflectedMethod<CheckMethodIdentifier, CheckExecutionContext> postExec | ||||||
|  |     ) { | ||||||
|  |  | ||||||
|  |     private static Logger logger = LoggerFactory.getLogger(AppliedCheck.class);  | ||||||
|  |  | ||||||
|  |     public boolean pre(Object context) { | ||||||
|  |         logger.debug("Running InteractionCheck preExec {} with annotation {}", preExec(), annotation()); | ||||||
|  |         return (boolean) preExec().run(new CheckExecutionContext(annotation, context)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void post(Object context) { | ||||||
|  |         logger.debug("Running InteractionCheck postExec {} with annotation {}", postExec(), annotation()); | ||||||
|  |         postExec().run(new CheckExecutionContext(annotation, context)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package net.tomatentum.marinara.checks; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.Annotation; | ||||||
|  |  | ||||||
|  | public record CheckExecutionContext(Annotation annotation, Object originalContext) { | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | package net.tomatentum.marinara.checks; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Type; | ||||||
|  |  | ||||||
|  | public record CheckMethodIdentifier(Type annotationType, CheckMethodType type) { | ||||||
|  |  | ||||||
|  |     public enum CheckMethodType { | ||||||
|  |         PRE("preExec"), | ||||||
|  |         POST("postExec"); | ||||||
|  |  | ||||||
|  |         private String methodName; | ||||||
|  |  | ||||||
|  |         private CheckMethodType(String methodName) { | ||||||
|  |             this.methodName = methodName; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String methodName() { | ||||||
|  |             return this.methodName; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public final String toString() { | ||||||
|  |         return "InteractionCheck(%s, %s)".formatted(annotationType, type); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | package net.tomatentum.marinara.checks; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.Annotation; | ||||||
|  |  | ||||||
|  | public interface InteractionCheck<A extends Annotation> { | ||||||
|  |  | ||||||
|  |     public boolean preExec(Object context, A annotation); | ||||||
|  |     public void postExec(Object context, A annotation); | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,74 @@ | |||||||
|  | package net.tomatentum.marinara.checks; | ||||||
|  |  | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.cutin.method.BestCandidateMethod; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier.CheckMethodType; | ||||||
|  | import net.tomatentum.marinara.parser.InteractionCheckClassParser; | ||||||
|  |  | ||||||
|  | public class InteractionCheckMethod extends BestCandidateMethod<CheckMethodIdentifier, CheckExecutionContext> { | ||||||
|  |  | ||||||
|  |     private CheckMethodIdentifier identifier; | ||||||
|  |  | ||||||
|  |     public InteractionCheckMethod( | ||||||
|  |             String methodName,  | ||||||
|  |             Object containingObject, | ||||||
|  |             CheckMethodIdentifier identifier | ||||||
|  |         ) { | ||||||
|  |         super(methodName, containingObject); | ||||||
|  |         this.identifier = identifier; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getParameter(CheckExecutionContext context, int index) { | ||||||
|  |         switch (index) { | ||||||
|  |             case 0: | ||||||
|  |                 return context.originalContext(); | ||||||
|  |             case 1: | ||||||
|  |                 return context.annotation(); | ||||||
|  |             default: | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public CheckMethodIdentifier identifier() { | ||||||
|  |         return this.identifier; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class InteractionCheckMethodFactory extends BestCandidateMethod.Factory<CheckMethodIdentifier, CheckExecutionContext> { | ||||||
|  |  | ||||||
|  |         private CheckMethodType type; | ||||||
|  |  | ||||||
|  |         public InteractionCheckMethodFactory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> methodContainer, CheckMethodType type) { | ||||||
|  |             super(methodContainer, type.methodName()); | ||||||
|  |             this.type = type; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public void addParser(Set<MethodParser> parsers) { | ||||||
|  |             parsers.add( | ||||||
|  |                 new InteractionCheckClassParser() | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         protected Optional<BestCandidateMethod<CheckMethodIdentifier, CheckExecutionContext>> bcProduce( | ||||||
|  |                     String methodName, | ||||||
|  |                     Object containingObject, | ||||||
|  |                     ParserResults parserResults | ||||||
|  |                 ) { | ||||||
|  |  | ||||||
|  |             CheckMethodIdentifier identifier = new CheckMethodIdentifier(parserResults.get(InteractionCheckClassParser.class), type); | ||||||
|  |             if (identifier.annotationType() == null) | ||||||
|  |                 return null; | ||||||
|  |             return Optional.of(new InteractionCheckMethod(methodName, containingObject, identifier)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| package net.tomatentum.marinara.command; |  | ||||||
|  |  | ||||||
| public class DiscordCommand { |  | ||||||
|      |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,19 @@ | |||||||
|  | package net.tomatentum.marinara.container; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactoryImpl; | ||||||
|  | import net.tomatentum.cutin.container.LoneMethodContainer; | ||||||
|  | import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier.CheckMethodType; | ||||||
|  | import net.tomatentum.marinara.checks.InteractionCheckMethod.InteractionCheckMethodFactory; | ||||||
|  |  | ||||||
|  | public class InteractionCheckContainer extends LoneMethodContainer<CheckMethodIdentifier, CheckExecutionContext> { | ||||||
|  |      | ||||||
|  |     public InteractionCheckContainer() { | ||||||
|  |         super(new ReflectedMethodFactoryImpl<>()); | ||||||
|  |         super.factory() | ||||||
|  |             .addFactory(new InteractionCheckMethodFactory(this, CheckMethodType.PRE)) | ||||||
|  |             .addFactory(new InteractionCheckMethodFactory(this, CheckMethodType.POST)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | package net.tomatentum.marinara.container; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactory; | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactoryImpl; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.cutin.container.MultiMethodContainer; | ||||||
|  | import net.tomatentum.cutin.method.ReflectedMethod; | ||||||
|  | import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.components.methods.ButtonInteractionMethod; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  | import net.tomatentum.marinara.interaction.methods.AutoCompleteInteractionMethod; | ||||||
|  | import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod; | ||||||
|  |  | ||||||
|  | public class InteractionMethodContainer extends MultiMethodContainer<InteractionIdentifier, Object> { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private static ReflectedMethodFactory<InteractionIdentifier, Object> createFactory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkRegistry, ContextObjectProvider cop) { | ||||||
|  |         return new ReflectedMethodFactoryImpl<InteractionIdentifier, Object>() | ||||||
|  |             .addFactory(new AutoCompleteInteractionMethod.Factory(checkRegistry, cop)) | ||||||
|  |             .addFactory(new SlashCommandInteractionMethod.Factory(checkRegistry, cop)) | ||||||
|  |             .addFactory(new ButtonInteractionMethod.Factory(checkRegistry, cop)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public InteractionMethodContainer(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkRegistry, ContextObjectProvider cop) { | ||||||
|  |         super(createFactory(checkRegistry, cop)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public MethodContainer<InteractionIdentifier, Object> addMethod(ReflectedMethod<InteractionIdentifier, Object> method) { | ||||||
|  |         super.identifiers().stream() | ||||||
|  |             .filter(method.identifier()::equals) | ||||||
|  |             .forEach(i -> InteractionIdentifier.tryAddDescriptions(i, method.identifier())); | ||||||
|  |         return super.addMethod(method); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,4 @@ | |||||||
|  | package net.tomatentum.marinara.interaction; | ||||||
|  |  | ||||||
|  | public interface InteractionHandler { | ||||||
|  | } | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | package net.tomatentum.marinara.interaction; | ||||||
|  |  | ||||||
|  | public enum InteractionType { | ||||||
|  |     COMMAND, | ||||||
|  |     AUTOCOMPLETE, | ||||||
|  |     BUTTON, | ||||||
|  |     SELECT_MENU, | ||||||
|  |     MODAL | ||||||
|  | } | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | @Target({ElementType.METHOD}) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface AutoComplete { | ||||||
|  |     public String value(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,19 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.structure.data.ButtonStructureData.ButtonStyle; | ||||||
|  |  | ||||||
|  | @Target({ElementType.METHOD}) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface Button { | ||||||
|  |     public String value(); //aka customId | ||||||
|  |     public String label() default "default_button"; | ||||||
|  |     public ButtonStyle style() default ButtonStyle.PRIMARY; | ||||||
|  |     public String url() default ""; | ||||||
|  |     public boolean disabled() default false; | ||||||
|  |     public String emoji() default ""; | ||||||
|  | } | ||||||
| @@ -0,0 +1,115 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands; | ||||||
|  |  | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.CommandChoices; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.choice.EnumChoices; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; | ||||||
|  |  | ||||||
|  | public class SlashCommandDefinition { | ||||||
|  |  | ||||||
|  |     public static SlashCommandOptionChoice[] getActualChoices(SlashCommandOption option) { | ||||||
|  |         CommandChoices choices = option.choices(); | ||||||
|  |         SlashCommandOptionChoice[] actualChoices = choices.value(); | ||||||
|  |         if (choices.value().length <= 0 && !choices.cenum().equals(PlaceHolderEnum.class)) | ||||||
|  |             actualChoices = EnumChoices.of(choices.cenum()).choices(); | ||||||
|  |         return actualChoices; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Set<InteractionIdentifier> entries; | ||||||
|  |     private RootCommandIdentifier rootIdentifier; | ||||||
|  |     private boolean isRootCommand; | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public SlashCommandDefinition(RootCommandIdentifier rootIdentifier) { | ||||||
|  |         this.entries = new HashSet<>(); | ||||||
|  |         this.rootIdentifier = rootIdentifier; | ||||||
|  |         this.isRootCommand = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public SlashCommandDefinition addIdentifier(InteractionIdentifier identifier) { | ||||||
|  |         RootCommandIdentifier rootIdentifier = (RootCommandIdentifier) identifier.rootNode(); | ||||||
|  |  | ||||||
|  |         if (!this.rootIdentifier.equals(rootIdentifier)) | ||||||
|  |             throw new IllegalArgumentException("Root Node did not match."); | ||||||
|  |  | ||||||
|  |         if (this.rootIdentifier.description() == null) | ||||||
|  |             this.rootIdentifier = rootIdentifier; | ||||||
|  |  | ||||||
|  |         if (!isRootCommand) | ||||||
|  |             this.isRootCommand = identifier.parent() == null ? true : false; | ||||||
|  |  | ||||||
|  |         if ((isRootCommand && identifier.parent() != null) || (!isRootCommand && identifier.parent() == null)) { | ||||||
|  |             throw new IllegalArgumentException(identifier.toString() + ": cannot have subcommands and rootcommand definitions together"); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         entries.add(identifier); | ||||||
|  |         this.logger.debug("Added identifer {} to command {}", identifier, rootIdentifier); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public SlashCommandIdentifier[] getSubCommandGroups() { | ||||||
|  |         if (isRootCommand) | ||||||
|  |             return null; | ||||||
|  |          | ||||||
|  |         List<InteractionIdentifier> subCommandGroups = entries().stream() | ||||||
|  |             .filter(x -> x.parent().parent() != null) | ||||||
|  |             .map(x -> x.parent()) | ||||||
|  |             .toList(); | ||||||
|  |  | ||||||
|  |         return subCommandGroups.toArray(SlashCommandIdentifier[]::new); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public SlashCommandIdentifier[] getSubCommands() { | ||||||
|  |         if (isRootCommand) | ||||||
|  |             return null; | ||||||
|  |         return entries.stream().filter(x -> x.parent() instanceof RootCommandIdentifier).toArray(SlashCommandIdentifier[]::new); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public SlashCommandIdentifier[] getSubCommands(String groupName) { | ||||||
|  |         if (isRootCommand) | ||||||
|  |             return null; | ||||||
|  |  | ||||||
|  |         List<InteractionIdentifier> subCommands = entries().stream() | ||||||
|  |             .filter(x -> x.parent().parent() != null && x.parent().name().equals(groupName)) | ||||||
|  |             .map(x -> x.parent().parent()) | ||||||
|  |             .toList(); | ||||||
|  |  | ||||||
|  |         return subCommands.toArray(SlashCommandIdentifier[]::new); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean equals(Object obj) { | ||||||
|  |         if (!(obj instanceof SlashCommandDefinition)) | ||||||
|  |             return false; | ||||||
|  |         SlashCommandDefinition other = (SlashCommandDefinition) obj; | ||||||
|  |         return this.rootIdentifier().equals(other.rootIdentifier()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public long[] serverIds() { | ||||||
|  |         return rootIdentifier().serverIds(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Set<InteractionIdentifier> entries() { | ||||||
|  |         return this.entries; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public RootCommandIdentifier rootIdentifier() { | ||||||
|  |         return rootIdentifier; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean isRootCommand() { | ||||||
|  |         return isRootCommand; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.annotation; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum; | ||||||
|  |  | ||||||
|  | public @interface CommandChoices { | ||||||
|  |     public SlashCommandOptionChoice[] value() default {}; | ||||||
|  |     public Class<? extends Enum<?>> cenum() default PlaceHolderEnum.class; | ||||||
|  | } | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.annotation; | ||||||
|  |  | ||||||
|  | public @interface Range { | ||||||
|  |     public double min() default Double.MIN_VALUE; | ||||||
|  |     public double max() default Double.MAX_VALUE; | ||||||
|  | } | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | @Target({ElementType.METHOD, ElementType.TYPE}) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface SlashCommand { | ||||||
|  |     public String name(); | ||||||
|  |     public String description() default ""; | ||||||
|  |     public SlashCommandOption[] options() default {}; | ||||||
|  |     public long[] serverIds() default {}; | ||||||
|  | } | ||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.annotation.AutoComplete; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||||
|  |  | ||||||
|  | @Target({ElementType.ANNOTATION_TYPE}) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface SlashCommandOption { | ||||||
|  |     public String name(); | ||||||
|  |     public String description() default ""; | ||||||
|  |     public SlashCommandOptionType type() default SlashCommandOptionType.STRING; | ||||||
|  |     public boolean required() default false; | ||||||
|  |     public AutoComplete[] autocompletes() default {}; | ||||||
|  |     public Range range() default @Range; | ||||||
|  |     public CommandChoices choices() default @CommandChoices; | ||||||
|  |  | ||||||
|  |     public enum PlaceHolderEnum { | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.annotation; | ||||||
|  |  | ||||||
|  | public @interface SlashCommandOptionChoice { | ||||||
|  |     public String name(); | ||||||
|  |     public long longValue() default Long.MAX_VALUE; | ||||||
|  |     public double doubleValue() default Double.MAX_VALUE; | ||||||
|  |     public String stringValue() default ""; | ||||||
|  | } | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | @Target({ElementType.METHOD}) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface SubCommand { | ||||||
|  |     public String name(); | ||||||
|  |     public String description() default ""; | ||||||
|  |     public SlashCommandOption[] options() default {}; | ||||||
|  | } | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | @Target({ElementType.METHOD, ElementType.TYPE}) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface SubCommandGroup { | ||||||
|  |     public String name(); | ||||||
|  |     public String description() default ""; | ||||||
|  | } | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.choice; | ||||||
|  |  | ||||||
|  | public interface ChoiceValueProvider<T> { | ||||||
|  |     T getChoiceValue(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.choice; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.InvocationTargetException; | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.lang.reflect.ParameterizedType; | ||||||
|  | import java.lang.reflect.Type; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | import io.leangen.geantyref.AnnotationFormatException; | ||||||
|  | import io.leangen.geantyref.GenericTypeReflector; | ||||||
|  | import io.leangen.geantyref.TypeFactory; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; | ||||||
|  |  | ||||||
|  | public record EnumChoices(Class<? extends Enum<?>> enumClass, ChoiceType type, SlashCommandOptionChoice[] choices) { | ||||||
|  |  | ||||||
|  |     private static Method method; | ||||||
|  |  | ||||||
|  |     static { | ||||||
|  |         try { | ||||||
|  |             method = ChoiceValueProvider.class.getMethod("getChoiceValue"); | ||||||
|  |         } catch (NoSuchMethodException | SecurityException e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static EnumChoices of(Class<? extends Enum<?>> enumClass) { | ||||||
|  |         if (!ChoiceValueProvider.class.isAssignableFrom(enumClass)) | ||||||
|  |             throw new IllegalArgumentException("Provided class needs to implement the ChoiceValueProvider interface."); | ||||||
|  |         ChoiceType type = parseChoiceType(enumClass); | ||||||
|  |         SlashCommandOptionChoice[] choices = parseChoices(enumClass, type); | ||||||
|  |         return new EnumChoices(enumClass, type, choices); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static ChoiceType parseChoiceType(Class<? extends Enum<?>> enumClass) { | ||||||
|  |         ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(enumClass, ChoiceValueProvider.class); | ||||||
|  |         Type typeParam = type.getActualTypeArguments()[0]; | ||||||
|  |  | ||||||
|  |         if (!(typeParam instanceof Class<?>)) | ||||||
|  |             throw new IllegalArgumentException("ChoiceValueProvider need either a String or Number type parameter."); | ||||||
|  |  | ||||||
|  |         if (Long.class.isAssignableFrom((Class<?>) typeParam)) | ||||||
|  |             return ChoiceType.INTEGER; | ||||||
|  |         if (Double.class.isAssignableFrom((Class<?>) typeParam)) | ||||||
|  |             return ChoiceType.DOUBLE; | ||||||
|  |         if (String.class.isAssignableFrom((Class<?>) typeParam)) | ||||||
|  |             return ChoiceType.String; | ||||||
|  |         throw new IllegalArgumentException("ChoiceValueProvider need either a String, Number or Decimal type parameter."); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static SlashCommandOptionChoice[] parseChoices(Class<? extends Enum<?>> enumClass, ChoiceType type) { | ||||||
|  |         Enum<? extends Enum<?>>[] constants = enumClass.getEnumConstants(); | ||||||
|  |         List<SlashCommandOptionChoice> choices = new ArrayList<>(); | ||||||
|  |         for (Enum<? extends Enum<?>> enumInstance : constants) { | ||||||
|  |             Object value; | ||||||
|  |             try { | ||||||
|  |                 value = method.invoke(enumInstance); | ||||||
|  |                 if (type.equals(ChoiceType.INTEGER)) | ||||||
|  |                     choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "longValue", value))); | ||||||
|  |                 if (type.equals(ChoiceType.DOUBLE)) | ||||||
|  |                     choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "doubleValue", value))); | ||||||
|  |                 if (type.equals(ChoiceType.String)) | ||||||
|  |                     choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "stringValue", value))); | ||||||
|  |             } catch (IllegalAccessException | InvocationTargetException | AnnotationFormatException e) { | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return choices.toArray(SlashCommandOptionChoice[]::new); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static enum ChoiceType { | ||||||
|  |         String, | ||||||
|  |         INTEGER, | ||||||
|  |         DOUBLE | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.option; | ||||||
|  |  | ||||||
|  | import java.util.Arrays; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  |  | ||||||
|  | public record AutocompleteOptionData(String name, Object input) { | ||||||
|  |      | ||||||
|  |     public String[] getAutocompleteRefs(SlashCommandOption[] options) { | ||||||
|  |         return Arrays.stream(options) | ||||||
|  |             .filter(o -> o.name().equals(this.name())) | ||||||
|  |             .flatMap(o -> Arrays.stream(o.autocompletes())) | ||||||
|  |             .map(a -> a.value()) | ||||||
|  |             .toArray(String[]::new); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.option; | ||||||
|  |  | ||||||
|  | public enum SlashCommandOptionType { | ||||||
|  |     SUB_COMMAND(1), | ||||||
|  |     SUB_COMMAND_GROUP(2), | ||||||
|  |     STRING(3), | ||||||
|  |     INTEGER(4), | ||||||
|  |     BOOLEAN(5), | ||||||
|  |     USER(6), | ||||||
|  |     CHANNEL(7), | ||||||
|  |     ROLE(8), | ||||||
|  |     MENTIONABLE(9), | ||||||
|  |     DOUBLE(10), | ||||||
|  |     ATTACHMENT(11), | ||||||
|  |     UNKNOWN(-1); | ||||||
|  |  | ||||||
|  |     private final int value; | ||||||
|  |  | ||||||
|  |     private SlashCommandOptionType(int value) { | ||||||
|  |         this.value = value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public int getValue() { | ||||||
|  |         return value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static SlashCommandOptionType fromValue(int value) { | ||||||
|  |         for (SlashCommandOptionType type : values()) { | ||||||
|  |             if (type.getValue() == value) { | ||||||
|  |                 return type; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return UNKNOWN; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,93 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.components.methods; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.cutin.method.ReflectedMethod; | ||||||
|  | import net.tomatentum.marinara.checks.AppliedCheck; | ||||||
|  | import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.methods.InteractionMethod; | ||||||
|  | import net.tomatentum.marinara.parser.ButtonParser; | ||||||
|  | import net.tomatentum.marinara.parser.InteractionCheckParser; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  |  | ||||||
|  | public class ButtonInteractionMethod extends InteractionMethod { | ||||||
|  |  | ||||||
|  |     private String customId; | ||||||
|  |     private ContextObjectProvider cop; | ||||||
|  |  | ||||||
|  |     private ButtonInteractionMethod( | ||||||
|  |             Method method,  | ||||||
|  |             InteractionHandler handler, | ||||||
|  |             List<AppliedCheck> appliedChecks, | ||||||
|  |             String customId,  | ||||||
|  |             ContextObjectProvider cop | ||||||
|  |         ) { | ||||||
|  |         super(method, handler, appliedChecks); | ||||||
|  |         this.customId = customId; | ||||||
|  |         this.cop = cop; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getParameter(Object context, int index) { | ||||||
|  |         Class<?> type = method().getParameterTypes()[index]; | ||||||
|  |         Object superResult = super.getParameter(context, index); | ||||||
|  |         if (superResult == null) | ||||||
|  |             return this.cop.getComponentContextObject(context, type); | ||||||
|  |         else | ||||||
|  |             return superResult; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier identifier() { | ||||||
|  |         return InteractionIdentifier.builder() | ||||||
|  |             .name(customId) | ||||||
|  |             .description("Button") | ||||||
|  |             .type(InteractionType.BUTTON) | ||||||
|  |             .build(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class Factory extends InteractionMethod.Factory { | ||||||
|  |  | ||||||
|  |         private ContextObjectProvider cop; | ||||||
|  |  | ||||||
|  |         public Factory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer, ContextObjectProvider cop) { | ||||||
|  |             super(checkContainer); | ||||||
|  |             this.cop = cop; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public Optional<ReflectedMethod<InteractionIdentifier, Object>> produce(Method method, Object containingObject, ParserResults parserResults) { | ||||||
|  |             if (!(containingObject instanceof InteractionHandler)) return Optional.empty(); | ||||||
|  |             String customId = parserResults.get(ButtonParser.class); | ||||||
|  |             if (customId == null) return Optional.empty(); | ||||||
|  |             return Optional.of(new ButtonInteractionMethod( | ||||||
|  |                 method,  | ||||||
|  |                 (InteractionHandler) containingObject, | ||||||
|  |                 parserResults.get(InteractionCheckParser.class), | ||||||
|  |                 customId, | ||||||
|  |                 this.cop | ||||||
|  |             )); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public void addParser(Set<MethodParser> parser) { | ||||||
|  |             super.addParser(parser); | ||||||
|  |  | ||||||
|  |             parser.add( | ||||||
|  |                 new ButtonParser() | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,146 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.ident; | ||||||
|  |  | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  |  | ||||||
|  | public class InteractionIdentifier { | ||||||
|  |  | ||||||
|  |     public static InteractionIdentifier.Builder builder() { | ||||||
|  |         return new InteractionIdentifier.Builder(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static RootCommandIdentifier.Builder rootBuilder() { | ||||||
|  |         return new RootCommandIdentifier.Builder(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static SlashCommandIdentifier.Builder slashBuilder() { | ||||||
|  |         return new SlashCommandIdentifier.Builder(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static InteractionIdentifier createHierarchy(InteractionType type, String... names) { | ||||||
|  |         InteractionIdentifier last = null; | ||||||
|  |         for (String string : names) { | ||||||
|  |             last = builder().name(string).type(type).parent(last).build(); | ||||||
|  |         } | ||||||
|  |         return last; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static void tryAddDescriptions(InteractionIdentifier receiver, InteractionIdentifier provider) { | ||||||
|  |         if (receiver == null || provider == null) | ||||||
|  |             return; | ||||||
|  |  | ||||||
|  |         if (receiver.description().isBlank()) | ||||||
|  |             receiver.description = provider.description(); | ||||||
|  |         tryAddDescriptions(receiver.parent(), provider.parent()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private InteractionIdentifier parent; | ||||||
|  |     private String name; | ||||||
|  |     private String description; | ||||||
|  |     private InteractionType type; | ||||||
|  |  | ||||||
|  |     InteractionIdentifier(InteractionIdentifier parent, String name, String description, InteractionType type) { | ||||||
|  |         this.parent = parent; | ||||||
|  |         this.name = name; | ||||||
|  |         this.description = description; | ||||||
|  |         this.type = type; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public InteractionIdentifier rootNode() { return rootNode(this); } | ||||||
|  |  | ||||||
|  |     private InteractionIdentifier rootNode(InteractionIdentifier identifier) { | ||||||
|  |         if (identifier.parent() == null) | ||||||
|  |             return identifier; | ||||||
|  |         return rootNode(identifier.parent()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String name() { | ||||||
|  |         return name; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String description() { | ||||||
|  |         return description; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public InteractionIdentifier parent() { | ||||||
|  |         return parent; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public InteractionType type() { | ||||||
|  |         return type; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean equals(Object obj) { | ||||||
|  |         if (!(obj instanceof InteractionIdentifier)) | ||||||
|  |             return false; | ||||||
|  |         InteractionIdentifier ident = (InteractionIdentifier) obj; | ||||||
|  |         if (!type().equals(ident.type())) | ||||||
|  |             return false; | ||||||
|  |         if (!name().equals(ident.name())) | ||||||
|  |             return false; | ||||||
|  |         return Objects.equals(parent(), ident.parent()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int hashCode() { | ||||||
|  |         return Objects.hash(type(), name(), parent()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String toString() { | ||||||
|  |         if (parent() == null) | ||||||
|  |             return name() + " - " + type(); | ||||||
|  |         return "%s:%s".formatted(name(), parent().toString()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class Builder { | ||||||
|  |         private InteractionIdentifier parent; | ||||||
|  |         private String name; | ||||||
|  |         private String description; | ||||||
|  |         private InteractionType type; | ||||||
|  |  | ||||||
|  |         public InteractionIdentifier parent() { | ||||||
|  |             return parent; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder parent(InteractionIdentifier parent) { | ||||||
|  |             this.parent = parent; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String name() { | ||||||
|  |             return name; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder name(String name) { | ||||||
|  |             this.name = name; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String description() { | ||||||
|  |             return description; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder description(String description) { | ||||||
|  |             this.description = description; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public InteractionType type() { | ||||||
|  |             return type; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder type(InteractionType type) { | ||||||
|  |             this.type = type; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public InteractionIdentifier build() { | ||||||
|  |             return new InteractionIdentifier(parent, name, description, type); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,99 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.ident; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  |  | ||||||
|  | public class RootCommandIdentifier extends SlashCommandIdentifier { | ||||||
|  |  | ||||||
|  |     private long[] serverIds; | ||||||
|  |  | ||||||
|  |     public RootCommandIdentifier( | ||||||
|  |             InteractionIdentifier parent,  | ||||||
|  |             String name,  | ||||||
|  |             String description,  | ||||||
|  |             InteractionType type, | ||||||
|  |             SlashCommandOption[] options,  | ||||||
|  |             long[] serverIds) { | ||||||
|  |         super(parent, name, description, type, options); | ||||||
|  |         this.serverIds = serverIds; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public long[] serverIds() { | ||||||
|  |         return serverIds; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class Builder { | ||||||
|  |         private InteractionIdentifier parent; | ||||||
|  |         private String name; | ||||||
|  |         private String description; | ||||||
|  |         private SlashCommandOption[] options; | ||||||
|  |         private long[] serverIds; | ||||||
|  |         private String[] autocompleteRef; | ||||||
|  |  | ||||||
|  |         public InteractionIdentifier parent() { | ||||||
|  |             return parent; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder parent(InteractionIdentifier parent) { | ||||||
|  |             this.parent = parent; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String name() { | ||||||
|  |             return name; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder name(String name) { | ||||||
|  |             this.name = name; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String description() { | ||||||
|  |             return this.description; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder description(String description) { | ||||||
|  |             this.description = description; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public SlashCommandOption[] options() { | ||||||
|  |             return this.options; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder options(SlashCommandOption[] options) { | ||||||
|  |             this.options = options; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public long[] serverIds() { | ||||||
|  |             return this.serverIds; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder serverIds(long[] serverIds) { | ||||||
|  |             this.serverIds = serverIds; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String[] autocompleteRef() { | ||||||
|  |             return this.autocompleteRef; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder autocompleteRef(String[] autocompleteRef) { | ||||||
|  |             this.autocompleteRef = autocompleteRef; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public SlashCommandIdentifier build() { | ||||||
|  |             return new RootCommandIdentifier( | ||||||
|  |                 parent,  | ||||||
|  |                 name,  | ||||||
|  |                 description,  | ||||||
|  |                 InteractionType.COMMAND,  | ||||||
|  |                 options,  | ||||||
|  |                 serverIds); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,87 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.ident; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  |  | ||||||
|  | public class SlashCommandIdentifier extends InteractionIdentifier { | ||||||
|  |  | ||||||
|  |     private SlashCommandOption[] options; | ||||||
|  |  | ||||||
|  |     protected SlashCommandIdentifier( | ||||||
|  |             InteractionIdentifier parent,  | ||||||
|  |             String name,  | ||||||
|  |             String description, | ||||||
|  |             InteractionType type, | ||||||
|  |             SlashCommandOption[] options) { | ||||||
|  |         super(parent, name, description, type); | ||||||
|  |         this.options = options; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public SlashCommandOption[] options() { | ||||||
|  |         return this.options; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class Builder { | ||||||
|  |         private InteractionIdentifier parent; | ||||||
|  |         private String name; | ||||||
|  |         private String description; | ||||||
|  |         private SlashCommandOption[] options; | ||||||
|  |         private String[] autocompleteRef; | ||||||
|  |  | ||||||
|  |         public InteractionIdentifier parent() { | ||||||
|  |             return parent; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder parent(InteractionIdentifier parent) { | ||||||
|  |             this.parent = parent; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String name() { | ||||||
|  |             return name; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder name(String name) { | ||||||
|  |             this.name = name; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String description() { | ||||||
|  |             return this.description; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder description(String description) { | ||||||
|  |             this.description = description; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public SlashCommandOption[] options() { | ||||||
|  |             return this.options; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder options(SlashCommandOption[] options) { | ||||||
|  |             this.options = options; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String[] autocompleteRef() { | ||||||
|  |             return this.autocompleteRef; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder autocompleteRef(String[] autocompleteRef) { | ||||||
|  |             this.autocompleteRef = autocompleteRef; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public SlashCommandIdentifier build() { | ||||||
|  |             return new SlashCommandIdentifier( | ||||||
|  |                 parent,  | ||||||
|  |                 name,  | ||||||
|  |                 description,  | ||||||
|  |                 InteractionType.COMMAND,  | ||||||
|  |                 options); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,98 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.methods; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.cutin.method.ReflectedMethod; | ||||||
|  | import net.tomatentum.marinara.checks.AppliedCheck; | ||||||
|  | import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.parser.AutocompleteParser; | ||||||
|  | import net.tomatentum.marinara.parser.InteractionCheckParser; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  |  | ||||||
|  | public class AutoCompleteInteractionMethod extends InteractionMethod { | ||||||
|  |  | ||||||
|  |     private String autocompleteRef; | ||||||
|  |     private ContextObjectProvider cop; | ||||||
|  |  | ||||||
|  |     private AutoCompleteInteractionMethod( | ||||||
|  |             Method method,  | ||||||
|  |             InteractionHandler handler, | ||||||
|  |             List<AppliedCheck> appliedChecks, | ||||||
|  |             String autocompleteRef, | ||||||
|  |             ContextObjectProvider cop | ||||||
|  |         ) { | ||||||
|  |         super(method, handler, appliedChecks); | ||||||
|  |         this.autocompleteRef = autocompleteRef; | ||||||
|  |         this.cop = cop; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getParameter(Object context, int index) { | ||||||
|  |         Class<?> type = method().getParameterTypes()[index]; | ||||||
|  |         Object contextObject = this.cop.getInteractionContextObject(context, type); | ||||||
|  |         if (contextObject != null) | ||||||
|  |             return contextObject; | ||||||
|  |  | ||||||
|  |         Object autocompleteOptionValue = this.cop.getAutocompleteFocusedOption(context).input(); | ||||||
|  |         if (type.isInstance(autocompleteOptionValue)) | ||||||
|  |             return autocompleteOptionValue; | ||||||
|  |  | ||||||
|  |         return super.getParameter(context, index); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier identifier() { | ||||||
|  |         return InteractionIdentifier.builder() | ||||||
|  |             .type(InteractionType.AUTOCOMPLETE) | ||||||
|  |             .name(autocompleteRef) | ||||||
|  |             .description("AUTOCOMPLETE") | ||||||
|  |             .build(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class Factory extends InteractionMethod.Factory { | ||||||
|  |  | ||||||
|  |         private ContextObjectProvider cop; | ||||||
|  |  | ||||||
|  |         public Factory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer, ContextObjectProvider cop) { | ||||||
|  |             super(checkContainer); | ||||||
|  |             this.cop = cop; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public Optional<ReflectedMethod<InteractionIdentifier, Object>> produce(Method method, Object containingObject, ParserResults parserResults) { | ||||||
|  |             if (!(containingObject instanceof InteractionHandler)) return Optional.empty(); | ||||||
|  |             String[] autocompletes = parserResults.get(AutocompleteParser.class); | ||||||
|  |             if (autocompletes.length <= 0) return Optional.empty(); | ||||||
|  |              | ||||||
|  |             return Optional.of(new AutoCompleteInteractionMethod( | ||||||
|  |                 method,  | ||||||
|  |                 (InteractionHandler) containingObject, | ||||||
|  |                 parserResults.get(InteractionCheckParser.class), | ||||||
|  |                 autocompletes[0], | ||||||
|  |                 cop | ||||||
|  |             )); | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public void addParser(Set<MethodParser> parser) { | ||||||
|  |             super.addParser(parser); | ||||||
|  |  | ||||||
|  |             parser.add( | ||||||
|  |                 new AutocompleteParser() | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.methods; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactory; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.cutin.method.ReflectedMethod; | ||||||
|  | import net.tomatentum.marinara.checks.AppliedCheck; | ||||||
|  | import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.parser.InteractionCheckParser; | ||||||
|  |  | ||||||
|  | public abstract class InteractionMethod extends ReflectedMethod<InteractionIdentifier, Object> { | ||||||
|  |  | ||||||
|  |     protected List<AppliedCheck> appliedChecks; | ||||||
|  |  | ||||||
|  |     protected InteractionMethod( | ||||||
|  |         Method method,  | ||||||
|  |         InteractionHandler handler, | ||||||
|  |         List<AppliedCheck> appliedChecks | ||||||
|  |         ) { | ||||||
|  |         super(method, handler); | ||||||
|  |         this.appliedChecks = appliedChecks; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getParameter(Object context, int index) { | ||||||
|  |         if (index == 0) | ||||||
|  |             return context; | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     public Object run(Object context) { | ||||||
|  |         Object result = null; | ||||||
|  |         if (this.appliedChecks.stream().filter(x -> !x.pre(context)).count() > 0) | ||||||
|  |             return null; | ||||||
|  |  | ||||||
|  |         result = super.run(context); | ||||||
|  |  | ||||||
|  |         this.appliedChecks.forEach(x -> x.post(context)); | ||||||
|  |  | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public List<AppliedCheck> appliedChecks() { | ||||||
|  |         return this.appliedChecks; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public abstract static class Factory implements ReflectedMethodFactory.Factory<InteractionIdentifier, Object> { | ||||||
|  |  | ||||||
|  |         private MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer; | ||||||
|  |  | ||||||
|  |         protected Factory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer) { | ||||||
|  |             this.checkContainer = checkContainer; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public void addParser(Set<MethodParser> parser) { | ||||||
|  |             parser.add( | ||||||
|  |                 new InteractionCheckParser(this.checkContainer) | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,87 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.methods; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.cutin.method.ReflectedMethod; | ||||||
|  | import net.tomatentum.marinara.checks.AppliedCheck; | ||||||
|  | import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; | ||||||
|  | import net.tomatentum.marinara.parser.InteractionCheckParser; | ||||||
|  | import net.tomatentum.marinara.parser.SlashCommandParser; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  |  | ||||||
|  | public class SlashCommandInteractionMethod extends InteractionMethod { | ||||||
|  |  | ||||||
|  |     private SlashCommandIdentifier identifier; | ||||||
|  |     private ContextObjectProvider cop; | ||||||
|  |  | ||||||
|  |     private SlashCommandInteractionMethod( | ||||||
|  |             Method method,  | ||||||
|  |             InteractionHandler handler, | ||||||
|  |             List<AppliedCheck> appliedChecks, | ||||||
|  |             SlashCommandIdentifier identifier, | ||||||
|  |             ContextObjectProvider cop | ||||||
|  |         ) { | ||||||
|  |         super(method, handler, appliedChecks); | ||||||
|  |         this.identifier = identifier; | ||||||
|  |         this.cop = cop; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getParameter(Object context, int index) { | ||||||
|  |         Object superResult = super.getParameter(context, index); | ||||||
|  |         if (superResult == null) | ||||||
|  |             return this.cop.convertCommandOption(context, identifier.options()[index-1].name()); | ||||||
|  |         else | ||||||
|  |             return superResult; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier identifier() { | ||||||
|  |         return identifier; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class Factory extends InteractionMethod.Factory { | ||||||
|  |  | ||||||
|  |         private ContextObjectProvider cop; | ||||||
|  |  | ||||||
|  |         public Factory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer, ContextObjectProvider cop) { | ||||||
|  |             super(checkContainer); | ||||||
|  |             this.cop = cop; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public Optional<ReflectedMethod<InteractionIdentifier, Object>> produce(Method method, Object containingObject, ParserResults parserResults) { | ||||||
|  |             if (!(containingObject instanceof InteractionHandler)) return Optional.empty(); | ||||||
|  |             SlashCommandIdentifier ident = parserResults.get(SlashCommandParser.class); | ||||||
|  |             if (ident == null) return Optional.empty(); | ||||||
|  |             return Optional.of(new SlashCommandInteractionMethod( | ||||||
|  |                 method,  | ||||||
|  |                 (InteractionHandler) containingObject, | ||||||
|  |                 parserResults.get(InteractionCheckParser.class), | ||||||
|  |                 ident, | ||||||
|  |                 cop | ||||||
|  |             )); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public void addParser(Set<MethodParser> parser) { | ||||||
|  |             super.addParser(parser); | ||||||
|  |  | ||||||
|  |             parser.add( | ||||||
|  |                 new SlashCommandParser() | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.processor; | ||||||
|  |  | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Objects; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  |  | ||||||
|  | public class AutocompleteInteractionProcessor extends InteractionMethodProcessor { | ||||||
|  |  | ||||||
|  |     private LibraryWrapper wrapper; | ||||||
|  |  | ||||||
|  |     public AutocompleteInteractionProcessor(LibraryWrapper wrapper, IdentifierProvider provider) { | ||||||
|  |         super(provider, Set.of(InteractionType.AUTOCOMPLETE)); | ||||||
|  |         this.wrapper = wrapper; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void processInteraction(Object context, MethodContainer<InteractionIdentifier, Object> container, InteractionIdentifier identifier) { | ||||||
|  |         Optional<InteractionIdentifier> oIdent = container.identifiers().stream() | ||||||
|  |             .filter(i -> convertToCommandIdentifier(identifier).equals(i)) | ||||||
|  |             .findFirst(); | ||||||
|  |         if (oIdent.isPresent() && oIdent.get() instanceof SlashCommandIdentifier sIdent) { | ||||||
|  |             List<String> autocompleteRefs = Arrays.asList(this.wrapper.getContextObjectProvider() | ||||||
|  |                 .getAutocompleteFocusedOption(context).getAutocompleteRefs(sIdent.options())); | ||||||
|  |             List<Object> results = container.methods().stream() | ||||||
|  |                 .filter(m -> m.identifier().type().equals(InteractionType.AUTOCOMPLETE)) | ||||||
|  |                 .filter(m -> autocompleteRefs.contains(m.identifier().name())) | ||||||
|  |                 .map(m -> m.run(context)) | ||||||
|  |                 .filter(Objects::nonNull) | ||||||
|  |                 .toList(); | ||||||
|  |             if (!results.isEmpty()) | ||||||
|  |                 this.wrapper.respondAutocomplete(context, results); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private InteractionIdentifier convertToCommandIdentifier(InteractionIdentifier identifier) { | ||||||
|  |         if (Objects.isNull(identifier)) | ||||||
|  |             return null; | ||||||
|  |         return InteractionIdentifier.builder() | ||||||
|  |             .type(InteractionType.COMMAND) | ||||||
|  |             .name(identifier.name()) | ||||||
|  |             .parent(convertToCommandIdentifier(identifier.parent())) | ||||||
|  |             .build(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.processor; | ||||||
|  |  | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  |  | ||||||
|  | public class DirectInteractionProcessor extends InteractionMethodProcessor { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public DirectInteractionProcessor(IdentifierProvider provider, InteractionType... types) { | ||||||
|  |         super(provider, Set.of(types)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected void processInteraction(Object context, MethodContainer<InteractionIdentifier, Object> container, InteractionIdentifier identifier) { | ||||||
|  |         container.findFor(identifier).forEach(m -> m.run(context)); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.processor; | ||||||
|  |  | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodProcessor; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  |  | ||||||
|  | public abstract class InteractionMethodProcessor implements MethodProcessor<InteractionIdentifier, Object> { | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     private IdentifierProvider provider; | ||||||
|  |     private Set<InteractionType> types; | ||||||
|  |  | ||||||
|  |     protected InteractionMethodProcessor(IdentifierProvider provider, Set<InteractionType> types) { | ||||||
|  |         this.provider = provider; | ||||||
|  |         this.types = types; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void process(Object context, MethodContainer<InteractionIdentifier, Object> container) { | ||||||
|  |         InteractionIdentifier identifier = this.provider.provide(context); | ||||||
|  |         if (!this.types.contains(identifier.type())) return; | ||||||
|  |         logger.debug("Processing {} : {} with context {}", identifier, identifier.type(), context); | ||||||
|  |         this.processInteraction(context, container, identifier); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected abstract void processInteraction( | ||||||
|  |         Object context,  | ||||||
|  |         MethodContainer<InteractionIdentifier, Object> container,  | ||||||
|  |         InteractionIdentifier identifier); | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | package net.tomatentum.marinara.parser; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.Arrays; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.util.ReflectionUtil; | ||||||
|  | import net.tomatentum.marinara.interaction.annotation.AutoComplete; | ||||||
|  |  | ||||||
|  | public class AutocompleteParser implements MethodParser { | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object parse(Method method, Object containingObject) { | ||||||
|  |         String[] autoCompletes = Arrays.stream(method.getAnnotationsByType(AutoComplete.class)) | ||||||
|  |             .map(AutoComplete::value) | ||||||
|  |             .toArray(String[]::new); | ||||||
|  |         if (autoCompletes.length > 0) | ||||||
|  |             logger.trace("Parsed AutoComplete annotations {} for method {}", autoCompletes, ReflectionUtil.getFullMethodName(method)); | ||||||
|  |         return autoCompletes; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | package net.tomatentum.marinara.parser; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.util.ReflectionUtil; | ||||||
|  | import net.tomatentum.marinara.interaction.annotation.Button; | ||||||
|  |  | ||||||
|  | public class ButtonParser implements MethodParser { | ||||||
|  |      | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public ButtonParser() { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object parse(Method method, Object containingObject) { | ||||||
|  |         Button button = method.getAnnotation(Button.class); | ||||||
|  |         if (button == null) return null; | ||||||
|  |         logger.trace("Parsed Button annotation {} for method {}", button, ReflectionUtil.getFullMethodName(method)); | ||||||
|  |         return button.value(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | package net.tomatentum.marinara.parser; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.lang.reflect.ParameterizedType; | ||||||
|  | import java.lang.reflect.Type; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import io.leangen.geantyref.GenericTypeReflector; | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.marinara.checks.InteractionCheck; | ||||||
|  |  | ||||||
|  | public class InteractionCheckClassParser implements MethodParser { | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object parse(Method method, Object containingObject) { | ||||||
|  |         ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(containingObject.getClass(), InteractionCheck.class); | ||||||
|  |         if (type == null) return null; | ||||||
|  |         Type typeParam = type.getActualTypeArguments().length == 1 ? type.getActualTypeArguments()[0] : null; | ||||||
|  |         if (typeParam != null) | ||||||
|  |             logger.trace("Parsed InteractionCheck Annotation {}", typeParam); | ||||||
|  |         return typeParam; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,49 @@ | |||||||
|  | package net.tomatentum.marinara.parser; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.Annotation; | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.container.MethodContainer; | ||||||
|  | import net.tomatentum.cutin.util.ReflectionUtil; | ||||||
|  | import net.tomatentum.marinara.checks.AppliedCheck; | ||||||
|  | import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||||
|  | import net.tomatentum.marinara.checks.CheckMethodIdentifier.CheckMethodType; | ||||||
|  |  | ||||||
|  | public class InteractionCheckParser implements MethodParser { | ||||||
|  |  | ||||||
|  |     private MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer; | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public InteractionCheckParser(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer) { | ||||||
|  |         this.checkContainer = checkContainer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object parse(Method method, Object containingObject) { | ||||||
|  |         Annotation[] annotations = method.getAnnotations(); | ||||||
|  |         return Arrays.stream(annotations) | ||||||
|  |             .map(a -> convertAnnotation(a, method)) | ||||||
|  |             .filter(Objects::nonNull) | ||||||
|  |             .toList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private AppliedCheck convertAnnotation(Annotation annotation, Method method) { | ||||||
|  |             var preExec = this.checkContainer.findFirstFor(new CheckMethodIdentifier(annotation.annotationType(), CheckMethodType.PRE)); | ||||||
|  |             var postExec = this.checkContainer.findFirstFor(new CheckMethodIdentifier(annotation.annotationType(), CheckMethodType.POST)); | ||||||
|  |             if (preExec.isPresent() && postExec.isPresent())  { | ||||||
|  |                 AppliedCheck appliedCheck = new AppliedCheck(annotation, preExec.get(), postExec.get()); | ||||||
|  |                 logger.trace("Parsed InteractionCheck {} for annotation {} for method {}", preExec.get().containingObject(), annotation, ReflectionUtil.getFullMethodName(method)); | ||||||
|  |                 return appliedCheck; | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,74 @@ | |||||||
|  | package net.tomatentum.marinara.parser; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.util.ReflectionUtil; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  |  | ||||||
|  | public class SlashCommandParser implements MethodParser { | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public SlashCommandParser() { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object parse(Method method, Object containingObject) { | ||||||
|  |         if (!method.isAnnotationPresent(SlashCommand.class) && !method.isAnnotationPresent(SubCommand.class)) return null; | ||||||
|  |         this.checkValidCommandMethod(method); | ||||||
|  |  | ||||||
|  |         SlashCommand cmd = ReflectionUtil.getAnnotation(method, SlashCommand.class); | ||||||
|  |         InteractionIdentifier lastIdentifier = InteractionIdentifier.rootBuilder() | ||||||
|  |             .name(cmd.name()) | ||||||
|  |             .description(cmd.description()) | ||||||
|  |             .options(cmd.options()) | ||||||
|  |             .serverIds(cmd.serverIds()) | ||||||
|  |             .build(); | ||||||
|  |  | ||||||
|  |         if (ReflectionUtil.isAnnotationPresent(method, SubCommandGroup.class)) { | ||||||
|  |             SubCommandGroup cmdGroup = ReflectionUtil.getAnnotation(method, SubCommandGroup.class); | ||||||
|  |             lastIdentifier = InteractionIdentifier.builder() | ||||||
|  |                 .name(cmdGroup.name()) | ||||||
|  |                 .description(cmdGroup.description()) | ||||||
|  |                 .type(InteractionType.COMMAND) | ||||||
|  |                 .parent(lastIdentifier) | ||||||
|  |                 .build(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (ReflectionUtil.isAnnotationPresent(method, SubCommand.class)) { | ||||||
|  |             SubCommand subCmd = ReflectionUtil.getAnnotation(method, SubCommand.class); | ||||||
|  |             lastIdentifier = InteractionIdentifier.slashBuilder() | ||||||
|  |                 .name(subCmd.name()) | ||||||
|  |                 .description(subCmd.description()) | ||||||
|  |                 .options(subCmd.options()) | ||||||
|  |                 .build(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         logger.trace("Parsed using SlashCommandParser for method {} with the result: {}", ReflectionUtil.getFullMethodName(method), lastIdentifier); | ||||||
|  |         return lastIdentifier; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void checkValidCommandMethod(Method method) { | ||||||
|  |         if (method.isAnnotationPresent(SlashCommand.class) &&  | ||||||
|  |             method.getDeclaringClass().isAnnotationPresent(SlashCommand.class)) { | ||||||
|  |             throw new RuntimeException(method.getName() + ": Can't have SlashCommand Annotation on Class and Method"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class)) | ||||||
|  |             throw new RuntimeException(method.getName() + ": Missing SlashCommand Annotation on either Class or Method"); | ||||||
|  |  | ||||||
|  |         if ((method.isAnnotationPresent(SubCommand.class) &&  | ||||||
|  |             !ReflectionUtil.isAnnotationPresent(method, SlashCommand.class))) { | ||||||
|  |             throw new RuntimeException(method.getName() + ": Missing SlashCommand Annotation on either Method or Class"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,64 @@ | |||||||
|  | package net.tomatentum.marinara.structure; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactory; | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||||
|  | import net.tomatentum.cutin.method.ReflectedMethod; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.structure.parser.ComponentStructureParser; | ||||||
|  |  | ||||||
|  | public class ComponentStructureMethod extends ReflectedMethod<InteractionIdentifier, Void> { | ||||||
|  |  | ||||||
|  |     private InteractionIdentifier identifier; | ||||||
|  |  | ||||||
|  |     private ComponentStructureMethod( | ||||||
|  |             Method method,  | ||||||
|  |             Object containingObject, | ||||||
|  |             InteractionIdentifier identifier | ||||||
|  |         ) { | ||||||
|  |         super(method, containingObject); | ||||||
|  |         this.identifier = identifier; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getParameter(Void context, int index) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier identifier() { | ||||||
|  |         return this.identifier; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class Factory implements ReflectedMethodFactory.Factory<InteractionIdentifier, Void> { | ||||||
|  |  | ||||||
|  |         private Class<? extends Object> buttonClass; | ||||||
|  |  | ||||||
|  |         public Factory(Class<? extends Object> buttonClass) { | ||||||
|  |             this.buttonClass = buttonClass; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public void addParser(Set<MethodParser> parser) { | ||||||
|  |             parser.add(new ComponentStructureParser(buttonClass)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public Optional<ReflectedMethod<InteractionIdentifier, Void>> produce( | ||||||
|  |                 Method method,  | ||||||
|  |                 Object containingObject, | ||||||
|  |                 ParserResults parserResults) { | ||||||
|  |             InteractionIdentifier identifier = parserResults.get(ComponentStructureParser.class); | ||||||
|  |             if (identifier == null) | ||||||
|  |                 return Optional.empty(); | ||||||
|  |              | ||||||
|  |             return Optional.of(new ComponentStructureMethod(method, containingObject, identifier)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | package net.tomatentum.marinara.structure; | ||||||
|  |  | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.ReflectedMethodFactoryImpl; | ||||||
|  | import net.tomatentum.cutin.container.LoneMethodContainer; | ||||||
|  | import net.tomatentum.cutin.method.ReflectedMethod; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  |  | ||||||
|  | public class MethodStructureProvider<B extends Object>  | ||||||
|  |     extends LoneMethodContainer<InteractionIdentifier, Void> | ||||||
|  |     implements StructureProvider<B> { | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     private Class<? extends B> buttonClass; | ||||||
|  |  | ||||||
|  |     public MethodStructureProvider(Class<? extends B> buttonClass) { | ||||||
|  |         super(new ReflectedMethodFactoryImpl<>()); | ||||||
|  |         super.factory().addFactory(new ComponentStructureMethod.Factory(buttonClass)); | ||||||
|  |         this.buttonClass = buttonClass; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public B button(InteractionIdentifier identifier) { | ||||||
|  |         Optional<ReflectedMethod<InteractionIdentifier, Void>> method = super.findFirstFor(identifier); | ||||||
|  |         if (method.isEmpty()) return null; | ||||||
|  |         try { | ||||||
|  |             return buttonClass.cast(method.get().run(null)); | ||||||
|  |         }catch (ClassCastException ex) { | ||||||
|  |             logger.warn("Structure Method {} return type did not match expected {}", method.get(), buttonClass); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,31 @@ | |||||||
|  | package net.tomatentum.marinara.structure; | ||||||
|  |  | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.TreeMap; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  |  | ||||||
|  | public class PriorityStructureProvider<B extends Object> implements StructureProvider<B> { | ||||||
|  |  | ||||||
|  |     private Map<Short, StructureProvider<B>> providerMap; | ||||||
|  |  | ||||||
|  |     public PriorityStructureProvider() { | ||||||
|  |         this.providerMap = new TreeMap<>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public PriorityStructureProvider<B> addProvider(StructureProvider<B> provider, short priority) { | ||||||
|  |         this.providerMap.put(priority, provider); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public B button(InteractionIdentifier identifier) { | ||||||
|  |         for (short priority : this.providerMap.keySet()) { | ||||||
|  |             B result = this.providerMap.get(priority).button(identifier); | ||||||
|  |             if (result != null) | ||||||
|  |                 return result; | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,31 @@ | |||||||
|  | package net.tomatentum.marinara.structure; | ||||||
|  |  | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.structure.data.ButtonStructureData; | ||||||
|  | import net.tomatentum.marinara.wrapper.ComponentStructureConverter; | ||||||
|  |  | ||||||
|  | public class StaticStructureProvider<B extends Object> implements StructureProvider<B> { | ||||||
|  |  | ||||||
|  |     private ComponentStructureConverter<B> converter; | ||||||
|  |  | ||||||
|  |     private Map<InteractionIdentifier, B> buttonMap; | ||||||
|  |  | ||||||
|  |     public StaticStructureProvider(ComponentStructureConverter<B> converter) { | ||||||
|  |         this.converter = converter; | ||||||
|  |         this.buttonMap = new HashMap<>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public StaticStructureProvider<B> addButton(InteractionIdentifier identifier, ButtonStructureData data) { | ||||||
|  |         this.buttonMap.put(identifier, this.converter.convertButton(data)); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     public B button(InteractionIdentifier identifier) { | ||||||
|  |         return this.buttonMap.get(identifier); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package net.tomatentum.marinara.structure; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  |  | ||||||
|  | public interface StructureProvider<B extends Object> { | ||||||
|  |     B button(InteractionIdentifier identifier); | ||||||
|  | } | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package net.tomatentum.marinara.structure.annotation; | ||||||
|  |  | ||||||
|  | public @interface ComponentStructure { | ||||||
|  |     public String customId(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | package net.tomatentum.marinara.structure.data; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.annotation.Button; | ||||||
|  |  | ||||||
|  | public record ButtonStructureData( | ||||||
|  |     String customId,  | ||||||
|  |     String label, | ||||||
|  |     ButtonStyle style, | ||||||
|  |     String url, | ||||||
|  |     boolean disabled, | ||||||
|  |     String emoji | ||||||
|  |     ) { | ||||||
|  |  | ||||||
|  |     public ButtonStructureData(Button button) { | ||||||
|  |         this(button.value(), button.label(), button.style(), button.url(), button.disabled(), button.emoji()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public enum ButtonStyle { | ||||||
|  |         PRIMARY, | ||||||
|  |         SECONDARY, | ||||||
|  |         SUCCESS, | ||||||
|  |         DANGER, | ||||||
|  |         LINK; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,44 @@ | |||||||
|  | package net.tomatentum.marinara.structure.parser; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.cutin.MethodParser; | ||||||
|  | import net.tomatentum.cutin.util.ReflectionUtil; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.structure.annotation.ComponentStructure; | ||||||
|  |  | ||||||
|  | public class ComponentStructureParser implements MethodParser { | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     private Class<? extends Object> buttonClass; | ||||||
|  |  | ||||||
|  |     public ComponentStructureParser(Class<? extends Object> buttonClass) { | ||||||
|  |         this.buttonClass = buttonClass; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object parse(Method method, Object containingObject) { | ||||||
|  |         if (!method.isAnnotationPresent(ComponentStructure.class))  | ||||||
|  |             return null; | ||||||
|  |         InteractionIdentifier.Builder builder = InteractionIdentifier.builder() | ||||||
|  |             .name(method.getAnnotation(ComponentStructure.class).customId()); | ||||||
|  |  | ||||||
|  |         if (buttonClass.isAssignableFrom(method.getReturnType())) | ||||||
|  |             builder.type(InteractionType.BUTTON); | ||||||
|  |  | ||||||
|  |         if (builder.type() == null) { | ||||||
|  |             logger.error("Structure Method {} return type did not match any of the required {}", String.join(","), buttonClass); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         logger.trace("Parsed Structure Method of {} with result {}", ReflectionUtil.getFullMethodName(method), builder.build()); | ||||||
|  |         return builder.build(); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,46 @@ | |||||||
|  | package net.tomatentum.marinara.util; | ||||||
|  |  | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.function.BiConsumer; | ||||||
|  | import java.util.function.Function; | ||||||
|  | import java.util.function.Supplier; | ||||||
|  |  | ||||||
|  | public class ObjectAggregator<O, K, V> { | ||||||
|  |     private Function<O, Iterable<K>> keySupplier; | ||||||
|  |     private BiConsumer<V, O> valueConsumer; | ||||||
|  |     private Function<K, V> defaultGenerator; | ||||||
|  |  | ||||||
|  |     public ObjectAggregator( | ||||||
|  |             Function<O, Iterable<K>> keySupplier,  | ||||||
|  |             BiConsumer<V, O> valueConsumer,  | ||||||
|  |             Function<K, V> defaultGenerator) { | ||||||
|  |         this.keySupplier = keySupplier; | ||||||
|  |         this.valueConsumer = valueConsumer; | ||||||
|  |         this.defaultGenerator = defaultGenerator; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public ObjectAggregator( | ||||||
|  |             Function<O, Iterable<K>> keySupplier,  | ||||||
|  |             BiConsumer<V, O> valueConsumer,  | ||||||
|  |             Supplier<V> defaultGenerator) { | ||||||
|  |         this.keySupplier = keySupplier; | ||||||
|  |         this.valueConsumer = valueConsumer; | ||||||
|  |         this.defaultGenerator = _ -> defaultGenerator.get(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Collection<V> aggregate(Iterable<O> iterator) { | ||||||
|  |         Map<K, V> map = new HashMap<>(); | ||||||
|  |         for (O element : iterator) { | ||||||
|  |             Iterable<K> keys = this.keySupplier.apply(element); | ||||||
|  |             for (K key : keys) { | ||||||
|  |                 V value = map.getOrDefault(key, this.defaultGenerator.apply(key)); | ||||||
|  |                 this.valueConsumer.accept(value, element); | ||||||
|  |                 map.putIfAbsent(key, value); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return map.values(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package net.tomatentum.marinara.util; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.function.Function; | ||||||
|  |  | ||||||
|  | public class ObjectListAggregator<O, K, V> extends ObjectAggregator<O, K, ArrayList<V>> { | ||||||
|  |  | ||||||
|  |     public ObjectListAggregator(Function<O, Iterable<K>> keySupplier, Function<O, V> valueConsumer) { | ||||||
|  |         super(keySupplier,  | ||||||
|  |         (l, o) -> l.add(valueConsumer.apply(o)), | ||||||
|  |         () -> new ArrayList<>()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,70 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; | ||||||
|  |  | ||||||
|  | public class CommandConverter<A extends Object, O extends Object, C extends Object> { | ||||||
|  |  | ||||||
|  |     public static <A, O, C> CommandConverter<A, O, C> of(Spec<A, O, C> spec) { | ||||||
|  |         return new CommandConverter<>(spec); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     private Spec<A, O, C> spec; | ||||||
|  |  | ||||||
|  |     CommandConverter(Spec<A, O, C> spec) { | ||||||
|  |         this.spec = spec; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public A convert(SlashCommandDefinition def) { | ||||||
|  |         logger.debug("Converting command {}", def); | ||||||
|  |         List<O> options = new ArrayList<>(); | ||||||
|  |         if (!def.isRootCommand()) { | ||||||
|  |             Arrays.stream(def.getSubCommands()).map(this::convertSubCommand).forEach(options::add); | ||||||
|  |             Arrays.stream(def.getSubCommandGroups()).map(x -> this.convertSubCommandGroup(def, x)).forEach(options::add); | ||||||
|  |         }else | ||||||
|  |             Arrays.stream(def.rootIdentifier().options()).map(this::convertOption).forEach(options::add); | ||||||
|  |  | ||||||
|  |         return spec.convertCommand(def.rootIdentifier(), options); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private O convertSubCommandGroup(SlashCommandDefinition def, InteractionIdentifier identifier) { | ||||||
|  |         logger.debug("Converting subCommandGroup {} of {}", identifier, def); | ||||||
|  |         SlashCommandIdentifier[] subCommands = def.getSubCommands(identifier.name()); | ||||||
|  |         List<O> convertedSubCommands = Arrays.stream(subCommands).map(this::convertSubCommand).toList(); | ||||||
|  |         return spec.convertSubCommandGroup(identifier, convertedSubCommands); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private O convertSubCommand(SlashCommandIdentifier identifier) { | ||||||
|  |         logger.debug("Converting subCommand {}", identifier); | ||||||
|  |         List<O> options = Arrays.stream(identifier.options()).map(this::convertOption).toList(); | ||||||
|  |         return spec.convertSubCommand(identifier, options); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private O convertOption(SlashCommandOption option) { | ||||||
|  |         logger.debug("Converting option {}", option); | ||||||
|  |         List<C> choices = Arrays.stream(SlashCommandDefinition.getActualChoices(option)).map(spec::convertChoice).toList(); | ||||||
|  |         return spec.convertOption(option, choices); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static interface Spec<A extends Object, O extends Object, C extends Object> { | ||||||
|  |  | ||||||
|  |         public A convertCommand(RootCommandIdentifier rootIdentifier, List<O>  options); | ||||||
|  |         public O convertSubCommandGroup(InteractionIdentifier identifier, List<O> subCommands); | ||||||
|  |         public O convertSubCommand(InteractionIdentifier identifier, List<O>  options); | ||||||
|  |         public O convertOption(SlashCommandOption option, List<C> choices); | ||||||
|  |         public C convertChoice(SlashCommandOptionChoice choice); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,55 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper; | ||||||
|  |  | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||||
|  | import net.tomatentum.marinara.util.ObjectAggregator; | ||||||
|  |  | ||||||
|  | public class CommandRegisterer<A extends Object> { | ||||||
|  |  | ||||||
|  |     public static <A extends Object> CommandRegisterer<A> of(Strategy<A> strategy, CommandConverter<A, ?, ?> converter) { | ||||||
|  |         return new CommandRegisterer<A>(strategy, converter); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     private Strategy<A> strategy; | ||||||
|  |     private CommandConverter<A, ?, ?> converter; | ||||||
|  |  | ||||||
|  |     CommandRegisterer(Strategy<A> strategy, CommandConverter<A, ?, ?> converter) { | ||||||
|  |         this.strategy = strategy; | ||||||
|  |         this.converter = converter; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void register(SlashCommandDefinition[] slashDefs) { | ||||||
|  |         Set<ServerCommandList<A>> serverCommands = new ObjectAggregator<SlashCommandDefinition, Long, ServerCommandList<A>>( | ||||||
|  |                 def -> Arrays.stream(def.serverIds()).boxed().toList(), | ||||||
|  |                 (l, o) -> { | ||||||
|  |                     logger.debug("Added {} for server ({}) registration.", o.rootIdentifier(), l.serverId()); | ||||||
|  |                     l.add(converter.convert(o)); | ||||||
|  |                 }, | ||||||
|  |                 ServerCommandList::new) | ||||||
|  |             .aggregate(Arrays.asList(slashDefs)).stream() | ||||||
|  |             .collect(Collectors.toSet()); | ||||||
|  |  | ||||||
|  |         Set<A> globalCommands = Arrays.stream(slashDefs) | ||||||
|  |             .filter(x -> x.serverIds().length <= 0) | ||||||
|  |             .peek(c -> logger.debug("Added {} for global registration.", c.rootIdentifier())) | ||||||
|  |             .map(converter::convert) | ||||||
|  |             .collect(Collectors.toSet()); | ||||||
|  |  | ||||||
|  |         serverCommands.forEach(strategy::registerServer); | ||||||
|  |         strategy.registerGlobal(globalCommands); | ||||||
|  |         logger.info("Registered all SlashCommands"); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public interface Strategy<A extends Object> { | ||||||
|  |         void registerServer(ServerCommandList<A> commands); | ||||||
|  |         void registerGlobal(Set<A> defs); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.structure.data.ButtonStructureData; | ||||||
|  |  | ||||||
|  | public interface ComponentStructureConverter<B extends Object> { | ||||||
|  |  | ||||||
|  |     B convertButton(ButtonStructureData data); | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.option.AutocompleteOptionData; | ||||||
|  |  | ||||||
|  | public interface ContextObjectProvider { | ||||||
|  |  | ||||||
|  |     public Object convertCommandOption(Object context, String optionName); | ||||||
|  |  | ||||||
|  |     public Object getComponentContextObject(Object context, Class<?> type); | ||||||
|  |     public Object getInteractionContextObject(Object context, Class<?> type); | ||||||
|  |  | ||||||
|  |     public AutocompleteOptionData getAutocompleteFocusedOption(Object context); | ||||||
|  | } | ||||||
| @@ -0,0 +1,75 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.ParameterizedType; | ||||||
|  | import java.lang.reflect.Type; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import io.leangen.geantyref.GenericTypeReflector; | ||||||
|  | import net.tomatentum.cutin.util.ReflectionUtil; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  |  | ||||||
|  | public class IdentifierProvider { | ||||||
|  |  | ||||||
|  |     public static IdentifierProvider of(Converter<?>... converter) { | ||||||
|  |         return new IdentifierProvider(Arrays.asList(converter)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     private Map<Class<?>, Converter<?>> converter; | ||||||
|  |  | ||||||
|  |     private IdentifierProvider(List<Converter<?>> converter) { | ||||||
|  |         this.converter = new HashMap<>(); | ||||||
|  |         for (Converter<?> conv : converter) { | ||||||
|  |             if (conv.getClass().getName().contains("$$Lambda")) | ||||||
|  |                 throw new IllegalArgumentException("Lambdas cannot be used for IdentifierConverter because of Type erasure."); | ||||||
|  |             Type type = GenericTypeReflector.getExactSuperType(conv.getClass(), Converter.class); | ||||||
|  |             Type parameterType = ((ParameterizedType) type).getActualTypeArguments()[0]; | ||||||
|  |             if (!(parameterType instanceof Class)) | ||||||
|  |                 throw new IllegalArgumentException("Only full Class types are supported by IdentiferConverters"); | ||||||
|  |             this.converter.put((Class<?>) parameterType, conv); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public InteractionIdentifier provide(Object context) { | ||||||
|  |         Type type = ReflectionUtil.getMostSpecificClass( | ||||||
|  |             converter.keySet().stream().filter(x -> x.isAssignableFrom(context.getClass())).toArray(Class<?>[]::new),  | ||||||
|  |             context.getClass()); | ||||||
|  |  | ||||||
|  |         if (type == null) | ||||||
|  |             logger.debug("No Identifier converter found for context {}", context.getClass()); | ||||||
|  |  | ||||||
|  |         @SuppressWarnings("unchecked") | ||||||
|  |         Converter<Object> conv = (Converter<Object>) converter.get(type); | ||||||
|  |  | ||||||
|  |         InteractionIdentifier result = conv.convert(context); | ||||||
|  |         logger.trace("Converted {} to {} using {}", context, result, conv); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @FunctionalInterface | ||||||
|  |     public interface Converter<T extends Object> { | ||||||
|  |         InteractionIdentifier convert(T context); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class LambdaWrapper<T extends Object> implements Converter<T> { | ||||||
|  |  | ||||||
|  |         private Converter<T> converter; | ||||||
|  |  | ||||||
|  |         LambdaWrapper(Converter<T> converter) { | ||||||
|  |             this.converter = converter; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public InteractionIdentifier convert(T context) { | ||||||
|  |             return this.converter.convert(context); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.function.Consumer; | ||||||
|  | public abstract class LibraryWrapper { | ||||||
|  |  | ||||||
|  |     private List<Consumer<Object>> interactionSubscriber; | ||||||
|  |  | ||||||
|  |     protected LibraryWrapper() { | ||||||
|  |         this.interactionSubscriber = new ArrayList<>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void handleInteraction(Object context) { | ||||||
|  |         interactionSubscriber.forEach(o -> o.accept(context)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void subscribeInteractions(Consumer<Object> consumer) { | ||||||
|  |         interactionSubscriber.add(consumer); | ||||||
|  |     } | ||||||
|  |     public void unsubscribeInteractions(Consumer<Object> consumer) { | ||||||
|  |         interactionSubscriber.remove(consumer); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public abstract CommandRegisterer<?> getRegisterer();   | ||||||
|  |     public abstract IdentifierProvider createIdentifierProvider(); | ||||||
|  |     public abstract ContextObjectProvider getContextObjectProvider(); | ||||||
|  |     public abstract void respondAutocomplete(Object context, List<Object> options); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper; | ||||||
|  |  | ||||||
|  | import java.util.HashSet; | ||||||
|  |  | ||||||
|  | public class ServerCommandList<A> extends HashSet<A>{ | ||||||
|  |  | ||||||
|  |     private long serverId; | ||||||
|  |      | ||||||
|  |     public ServerCommandList(long serverId) { | ||||||
|  |         this.serverId = serverId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public long serverId() { | ||||||
|  |         return serverId; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -11,4 +11,11 @@ plugins { | |||||||
| } | } | ||||||
|  |  | ||||||
| rootProject.name = "Marinara" | rootProject.name = "Marinara" | ||||||
| include("lib") | include(":lib") | ||||||
|  | include(":wrapper-javacord") | ||||||
|  | include(":wrapper-discord4j") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | project(":wrapper-javacord").projectDir = file("wrapper/javacord") | ||||||
|  | project(":wrapper-discord4j").projectDir = file("wrapper/discord4j") | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								wrapper/discord4j/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								wrapper/discord4j/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | /* | ||||||
|  |  * This file was generated by the Gradle 'init' task. | ||||||
|  |  * | ||||||
|  |  * This generated file contains a sample Java library project to get you started. | ||||||
|  |  * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.8/userguide/building_java_projects.html in the Gradle documentation. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | plugins { | ||||||
|  |     // Apply the java-library plugin for API and implementation separation. | ||||||
|  |     `java-library` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     // Use JUnit Jupiter for testing. | ||||||
|  |     testImplementation(libs.junit.jupiter) | ||||||
|  |     testImplementation(libs.mockito) | ||||||
|  |     testImplementation(libs.discord4j) | ||||||
|  |  | ||||||
|  |     testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||||||
|  |     testImplementation(libs.slf4j.simple) | ||||||
|  |  | ||||||
|  |     implementation(libs.slf4j) | ||||||
|  |     implementation(libs.discord4j) { | ||||||
|  |         exclude(module="discord4j-voice") | ||||||
|  |     } | ||||||
|  |     implementation(libs.geantyref) | ||||||
|  |     implementation(project(":lib")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply a specific Java toolchain to ease working on different environments. | ||||||
|  | java { | ||||||
|  |     toolchain { | ||||||
|  |         languageVersion = JavaLanguageVersion.of(23) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tasks.named<Test>("test") { | ||||||
|  |     // Use JUnit Platform for unit tests. | ||||||
|  |     useJUnitPlatform() | ||||||
|  | } | ||||||
| @@ -0,0 +1,105 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||||
|  | import discord4j.core.event.domain.interaction.ComponentInteractionEvent; | ||||||
|  | import discord4j.core.event.domain.interaction.InteractionCreateEvent; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.option.AutocompleteOptionData; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  |  | ||||||
|  | public class Discord4JContextObjectProvider implements ContextObjectProvider { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object convertCommandOption(Object context, String optionName) { | ||||||
|  |         if (!(context instanceof ChatInputInteractionEvent)) | ||||||
|  |             return null; | ||||||
|  |         ChatInputInteractionEvent interactionEvent = (ChatInputInteractionEvent) context; | ||||||
|  |  | ||||||
|  |         List<ApplicationCommandInteractionOption> subOptions = Discord4JWrapper.SUB_FILTER.apply(interactionEvent.getOptions()); | ||||||
|  |  | ||||||
|  |         if (subOptions.isEmpty()) | ||||||
|  |             return getOptionValue(interactionEvent.getOption(optionName).get()); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         ApplicationCommandInteractionOption subCommandOption = interactionEvent.getOptions().getFirst(); | ||||||
|  |         subOptions = Discord4JWrapper.SUB_FILTER.apply(subCommandOption.getOptions()); | ||||||
|  |  | ||||||
|  |         if (!subOptions.isEmpty()) | ||||||
|  |             subCommandOption = subOptions.getFirst(); | ||||||
|  |  | ||||||
|  |         return getOptionValue(interactionEvent.getOption(optionName).get()); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Object getOptionValue(ApplicationCommandInteractionOption option) { | ||||||
|  |         if (!option.getValue().isPresent()) | ||||||
|  |             return null; | ||||||
|  |         SlashCommandOptionType type = getOptionType(option); | ||||||
|  |  | ||||||
|  |         switch (type) { | ||||||
|  |             case ATTACHMENT: | ||||||
|  |                 return option.getValue().get().asAttachment(); | ||||||
|  |             case BOOLEAN: | ||||||
|  |                 return option.getValue().get().asBoolean(); | ||||||
|  |             case CHANNEL: | ||||||
|  |                 return option.getValue().get().asChannel(); | ||||||
|  |             case DOUBLE: | ||||||
|  |                 return option.getValue().get().asDouble(); | ||||||
|  |             case INTEGER: | ||||||
|  |                 return option.getValue().get().asLong(); | ||||||
|  |             case MENTIONABLE: | ||||||
|  |                 return option.getValue().get().asSnowflake(); | ||||||
|  |             case ROLE: | ||||||
|  |                 return option.getValue().get().asRole(); | ||||||
|  |             case STRING: | ||||||
|  |                 return option.getValue().get().asString(); | ||||||
|  |             case USER: | ||||||
|  |                 return option.getValue().get().asUser(); | ||||||
|  |             default: | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private SlashCommandOptionType getOptionType(ApplicationCommandInteractionOption option) { | ||||||
|  |         return SlashCommandOptionType.fromValue(option.getType().getValue()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getComponentContextObject(Object context, Class<?> type) { | ||||||
|  |         ComponentInteractionEvent componentInteractionEvent = (ComponentInteractionEvent) context; | ||||||
|  |         switch (type.getName()) { | ||||||
|  |             case "discord4j.core.object.entity.Message": | ||||||
|  |                 return componentInteractionEvent.getMessage().orElse(null); | ||||||
|  |             default: | ||||||
|  |                 return getInteractionContextObject(context, type); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getInteractionContextObject(Object context, Class<?> type) { | ||||||
|  |         InteractionCreateEvent interactionEvent = (InteractionCreateEvent) context; | ||||||
|  |         switch (type.getName()) { | ||||||
|  |             case "discord4j.core.object.entity.channel.MessageChannel": | ||||||
|  |                 return interactionEvent.getInteraction().getChannel().block(); | ||||||
|  |             case "discord4j.core.object.entity.Guild": | ||||||
|  |                 return interactionEvent.getInteraction().getGuild().block(); | ||||||
|  |             case "discord4j.core.object.entity.Member": | ||||||
|  |                 return interactionEvent.getInteraction().getMember().orElse(null); | ||||||
|  |             case "discord4j.core.object.entity.User": | ||||||
|  |                 return interactionEvent.getInteraction().getUser(); | ||||||
|  |             default: | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public AutocompleteOptionData getAutocompleteFocusedOption(Object context) { | ||||||
|  |         ApplicationCommandInteractionOption option = ((ChatInputAutoCompleteEvent) context).getFocusedOption(); | ||||||
|  |         return new AutocompleteOptionData(option.getName(), getOptionValue(option)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,79 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import discord4j.core.object.command.ApplicationCommandOption.Type; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandOptionChoiceData; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandOptionData; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandRequest; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.CommandConverter; | ||||||
|  |  | ||||||
|  | public class Discord4JConverterSpec implements CommandConverter.Spec<ApplicationCommandRequest, ApplicationCommandOptionData, ApplicationCommandOptionChoiceData> { | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public ApplicationCommandRequest convertCommand(RootCommandIdentifier rootIdentifier, | ||||||
|  | 			List<ApplicationCommandOptionData> options) { | ||||||
|  |         return ApplicationCommandRequest.builder() | ||||||
|  |             .name(rootIdentifier.name()) | ||||||
|  |             .description(rootIdentifier.description()) | ||||||
|  |             .options(options) | ||||||
|  |             .build(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public ApplicationCommandOptionData convertSubCommandGroup(InteractionIdentifier identifier, | ||||||
|  | 			List<ApplicationCommandOptionData> subCommands) { | ||||||
|  | 		return ApplicationCommandOptionData.builder() | ||||||
|  | 			.type(Type.SUB_COMMAND_GROUP.getValue()) | ||||||
|  | 			.name(identifier.name()) | ||||||
|  | 			.description(identifier.description()) | ||||||
|  | 			.options(subCommands) | ||||||
|  | 			.build(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public ApplicationCommandOptionData convertSubCommand(InteractionIdentifier identifier, | ||||||
|  | 			List<ApplicationCommandOptionData> options) { | ||||||
|  |         return ApplicationCommandOptionData.builder() | ||||||
|  |             .type(Type.SUB_COMMAND_GROUP.getValue()) | ||||||
|  |             .name(identifier.name()) | ||||||
|  |             .description(identifier.description()) | ||||||
|  |             .options(options) | ||||||
|  |             .build(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public ApplicationCommandOptionData convertOption(SlashCommandOption option, | ||||||
|  | 			List<ApplicationCommandOptionChoiceData> choices) { | ||||||
|  | 		Type type = Type.of(option.type().getValue()); | ||||||
|  |         return ApplicationCommandOptionData.builder() | ||||||
|  |             .type(type.getValue()) | ||||||
|  |             .name(option.name()) | ||||||
|  |             .description(option.description()) | ||||||
|  |             .required(option.required()) | ||||||
|  |             .autocomplete(option.autocompletes().length > 0) | ||||||
|  | 			.minLength((int) option.range().min()) | ||||||
|  | 			.minValue(option.range().min()) | ||||||
|  | 			.maxLength((int)option.range().max()) | ||||||
|  | 			.maxValue(option.range().max()) | ||||||
|  |             .choices(choices) | ||||||
|  |             .build(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public ApplicationCommandOptionChoiceData convertChoice(SlashCommandOptionChoice choice) { | ||||||
|  | 		var builder = ApplicationCommandOptionChoiceData.builder().name(choice.name()); | ||||||
|  | 		if (choice.longValue() != Long.MAX_VALUE) | ||||||
|  | 			builder.value(choice.longValue()); | ||||||
|  | 		if (choice.doubleValue() != Double.MAX_VALUE) | ||||||
|  | 			builder.value(choice.doubleValue()); | ||||||
|  | 		if (!choice.stringValue().isEmpty()) | ||||||
|  | 			builder.value(choice.stringValue()); | ||||||
|  | 		return builder.build(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | } | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import discord4j.core.GatewayDiscordClient; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandRequest; | ||||||
|  | import discord4j.rest.service.ApplicationService; | ||||||
|  | import net.tomatentum.marinara.wrapper.CommandRegisterer; | ||||||
|  | import net.tomatentum.marinara.wrapper.ServerCommandList; | ||||||
|  |  | ||||||
|  | public class Discord4JRegistererStrategy implements CommandRegisterer.Strategy<ApplicationCommandRequest> { | ||||||
|  |  | ||||||
|  |     private ApplicationService appService; | ||||||
|  |     private long applicationId; | ||||||
|  |  | ||||||
|  |     public Discord4JRegistererStrategy(GatewayDiscordClient api) { | ||||||
|  |         this.appService = api.getRestClient().getApplicationService(); | ||||||
|  |         this.applicationId = api.getRestClient().getApplicationId().block(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void registerServer(ServerCommandList<ApplicationCommandRequest> commands) { | ||||||
|  |         appService.bulkOverwriteGuildApplicationCommand(applicationId, commands.serverId(), new ArrayList<>(commands)); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void registerGlobal(Set<ApplicationCommandRequest> defs) { | ||||||
|  |         appService.bulkOverwriteGlobalApplicationCommand(applicationId, new ArrayList<>(defs)); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,86 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.function.UnaryOperator; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import discord4j.core.GatewayDiscordClient; | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||||
|  | import discord4j.core.event.domain.interaction.InteractionCreateEvent; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandOption.Type; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandOptionChoiceData; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandRequest; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.wrapper.CommandConverter; | ||||||
|  | import net.tomatentum.marinara.wrapper.CommandRegisterer; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.identifierconverter.AutocompleteIdentifierConverter; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.identifierconverter.ButtonIdentifierConverter; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.identifierconverter.SlashCommandIdentifierConverter; | ||||||
|  |  | ||||||
|  | public class Discord4JWrapper extends LibraryWrapper { | ||||||
|  |     public static final UnaryOperator<List<ApplicationCommandInteractionOption>> SUB_FILTER = i -> | ||||||
|  |         i.stream() | ||||||
|  |             .filter(o -> o.getType().equals(Type.SUB_COMMAND) || o.getType().equals(Type.SUB_COMMAND_GROUP)) | ||||||
|  |             .toList(); | ||||||
|  |      | ||||||
|  |     public static final UnaryOperator<List<ApplicationCommandInteractionOption>> ARG_FILTER = i -> | ||||||
|  |             i.stream() | ||||||
|  |                 .filter(o -> !o.getType().equals(Type.SUB_COMMAND) && !o.getType().equals(Type.SUB_COMMAND_GROUP)) | ||||||
|  |                 .toList(); | ||||||
|  |  | ||||||
|  |     private Discord4JContextObjectProvider contextObjectProvider; | ||||||
|  |     private CommandRegisterer<ApplicationCommandRequest> commandRegisterer; | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public Discord4JWrapper(GatewayDiscordClient api) { | ||||||
|  |         this.contextObjectProvider = new Discord4JContextObjectProvider(); | ||||||
|  |         var converter = CommandConverter.of(new Discord4JConverterSpec()); | ||||||
|  |  | ||||||
|  |         if (api != null) { | ||||||
|  |             this.commandRegisterer = CommandRegisterer.of(new Discord4JRegistererStrategy(api), converter); | ||||||
|  |             api.on(InteractionCreateEvent.class) | ||||||
|  |                 .subscribe(event -> handleInteraction(event)); | ||||||
|  |         }else | ||||||
|  |             logger.warn("GatewayDiscordClient was null so no Events were subscribed to."); | ||||||
|  |              | ||||||
|  |         logger.info("Discord4J wrapper loaded!"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public CommandRegisterer<?> getRegisterer() { | ||||||
|  |         return this.commandRegisterer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public IdentifierProvider createIdentifierProvider() { | ||||||
|  |         return IdentifierProvider.of( | ||||||
|  |             new SlashCommandIdentifierConverter(), | ||||||
|  |             new AutocompleteIdentifierConverter(), | ||||||
|  |             new ButtonIdentifierConverter() | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public ContextObjectProvider getContextObjectProvider() { | ||||||
|  |         return this.contextObjectProvider; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void respondAutocomplete(Object context, List<Object> options) { | ||||||
|  |         if (context instanceof ChatInputAutoCompleteEvent event) { | ||||||
|  |             List<ApplicationCommandOptionChoiceData> choices = options.stream() | ||||||
|  |                 .filter(ApplicationCommandOptionChoiceData.class::isInstance) | ||||||
|  |                 .map(o -> (ApplicationCommandOptionChoiceData)o) | ||||||
|  |                 .toList(); | ||||||
|  |             event.respondWithSuggestions(choices); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j.checks; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.InteractionCreateEvent; | ||||||
|  | import discord4j.core.object.entity.Member; | ||||||
|  | import discord4j.rest.util.Permission; | ||||||
|  | import discord4j.rest.util.PermissionSet; | ||||||
|  | import net.tomatentum.marinara.checks.InteractionCheck; | ||||||
|  |  | ||||||
|  | public class PermissionCheck implements InteractionCheck<PermissionCheck.HasPermission> { | ||||||
|  |  | ||||||
|  |     @Retention(RetentionPolicy.RUNTIME) | ||||||
|  |     @Target(ElementType.METHOD) | ||||||
|  |     public static @interface HasPermission { | ||||||
|  |         public Permission[] value(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean preExec(Object context, HasPermission annotation) { | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'preExec'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean preExec(InteractionCreateEvent context, HasPermission annotation) { | ||||||
|  |         Optional<Member> member = context.getInteraction().getMember(); | ||||||
|  |         if (member.isEmpty()) | ||||||
|  |             return false; | ||||||
|  |         PermissionSet ownPerms = PermissionSet.of(annotation.value()); | ||||||
|  |         PermissionSet permSet = member.get().getBasePermissions().block(); | ||||||
|  |         return permSet.containsAll(ownPerms); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void postExec(Object context, HasPermission annotation) { | ||||||
|  |          | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j.identifierconverter; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||||
|  |  | ||||||
|  | public class AutocompleteIdentifierConverter implements IdentifierProvider.Converter<ChatInputAutoCompleteEvent> { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier convert(ChatInputAutoCompleteEvent context) { | ||||||
|  |         InteractionIdentifier last = InteractionIdentifier.builder() | ||||||
|  |             .type(InteractionType.AUTOCOMPLETE) | ||||||
|  |             .name(context.getCommandName()) | ||||||
|  |             .build(); | ||||||
|  |  | ||||||
|  |         List<ApplicationCommandInteractionOption> options = Discord4JWrapper.SUB_FILTER.apply(context.getOptions()); | ||||||
|  |         if (!options.isEmpty()) { | ||||||
|  |             last = InteractionIdentifier.builder() | ||||||
|  |                 .type(InteractionType.AUTOCOMPLETE) | ||||||
|  |                 .name(options.getFirst().getName()) | ||||||
|  |                 .parent(last) | ||||||
|  |                 .build(); | ||||||
|  |  | ||||||
|  |             List<ApplicationCommandInteractionOption> subOptions = Discord4JWrapper.SUB_FILTER.apply(options.getFirst().getOptions()); | ||||||
|  |             if (!subOptions.isEmpty()) | ||||||
|  |                 last = InteractionIdentifier.builder() | ||||||
|  |                     .type(InteractionType.AUTOCOMPLETE) | ||||||
|  |                     .name(subOptions.getFirst().getName()) | ||||||
|  |                     .parent(last) | ||||||
|  |                     .build(); | ||||||
|  |         } | ||||||
|  |         return last; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j.identifierconverter; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  |  | ||||||
|  | public class ButtonIdentifierConverter implements IdentifierProvider.Converter<ButtonInteractionEvent> { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier convert(ButtonInteractionEvent context) { | ||||||
|  |         return InteractionIdentifier.builder().name(context.getCustomId()).type(InteractionType.BUTTON).build(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j.identifierconverter; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||||
|  |  | ||||||
|  | public class SlashCommandIdentifierConverter implements IdentifierProvider.Converter<ChatInputInteractionEvent> { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier convert(ChatInputInteractionEvent context) { | ||||||
|  |         InteractionIdentifier last = InteractionIdentifier.builder() | ||||||
|  |             .type(InteractionType.COMMAND) | ||||||
|  |             .name(context.getCommandName()) | ||||||
|  |             .build(); | ||||||
|  |  | ||||||
|  |         List<ApplicationCommandInteractionOption> options = Discord4JWrapper.SUB_FILTER.apply(context.getOptions()); | ||||||
|  |         if (!options.isEmpty()) { | ||||||
|  |             last = InteractionIdentifier.builder() | ||||||
|  |                 .type(InteractionType.COMMAND) | ||||||
|  |                 .name(options.getFirst().getName()) | ||||||
|  |                 .parent(last) | ||||||
|  |                 .build(); | ||||||
|  |  | ||||||
|  |             List<ApplicationCommandInteractionOption> subOptions = Discord4JWrapper.SUB_FILTER.apply(options.getFirst().getOptions()); | ||||||
|  |             if (!subOptions.isEmpty()) | ||||||
|  |                 last = InteractionIdentifier.builder() | ||||||
|  |                     .type(InteractionType.COMMAND) | ||||||
|  |                     .name(subOptions.getFirst().getName()) | ||||||
|  |                     .parent(last) | ||||||
|  |                     .build(); | ||||||
|  |         } | ||||||
|  |         return last; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,45 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import static org.mockito.ArgumentMatchers.any; | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.verify; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOptionValue; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandOption.Type; | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||||
|  |  | ||||||
|  | class AutoCompleteTest { | ||||||
|  |      | ||||||
|  |     @Test | ||||||
|  |     void testAutocomplete() { | ||||||
|  |         ApplicationCommandInteractionOption optionMock = mock(); | ||||||
|  |         ChatInputAutoCompleteEvent autoCompleteEventMock = mock(); | ||||||
|  |  | ||||||
|  |         when(optionMock.getName()).thenReturn("foo"); | ||||||
|  |         when(optionMock.getType()).thenReturn(Type.STRING); | ||||||
|  |         when(optionMock.getValue()).thenReturn( | ||||||
|  |             Optional.of( | ||||||
|  |                 new ApplicationCommandInteractionOptionValue(null, null, Type.STRING.getValue(), "test", null) | ||||||
|  |                 )); | ||||||
|  |  | ||||||
|  |         when(autoCompleteEventMock.getCommandName()).thenReturn("test"); | ||||||
|  |         when(autoCompleteEventMock.getOptions()).thenReturn(new ArrayList<>()); | ||||||
|  |         when(autoCompleteEventMock.getFocusedOption()).thenReturn(optionMock); | ||||||
|  |  | ||||||
|  |         LibraryWrapper wrapper = new Discord4JWrapper(null); //null okay as we don't use the discord API in this test. | ||||||
|  |         Marinara marinara = Marinara.load(wrapper); | ||||||
|  |         marinara.getInteractionContainer().addAllMethods(new TestAutocomplete()); | ||||||
|  |         wrapper.handleInteraction(autoCompleteEventMock); | ||||||
|  |         verify(autoCompleteEventMock).respondWithSuggestions(any()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertTrue; | ||||||
|  |  | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | import org.junit.jupiter.api.TestInstance; | ||||||
|  | import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.test.discord4j.mocks.CommonMocks; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||||
|  |  | ||||||
|  | @TestInstance(Lifecycle.PER_CLASS) | ||||||
|  | class ButtonTest { | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     void testButtonExecution() { | ||||||
|  |         ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("test"); | ||||||
|  |  | ||||||
|  |         LibraryWrapper wrapper = new Discord4JWrapper(null); //null okay as we don't use the discord API in this test. | ||||||
|  |         Marinara marinara = Marinara.load(wrapper); | ||||||
|  |         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||||
|  |         wrapper.handleInteraction(buttonEventMock); | ||||||
|  |         assertTrue(TestButton.didRun); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,70 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertFalse; | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertTrue; | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  |  | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | import org.junit.jupiter.api.TestInstance; | ||||||
|  | import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||||
|  | import discord4j.core.object.command.Interaction; | ||||||
|  | import discord4j.core.object.entity.Member; | ||||||
|  | import discord4j.rest.util.Permission; | ||||||
|  | import discord4j.rest.util.PermissionSet; | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.test.discord4j.mocks.CommonMocks; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.checks.PermissionCheck; | ||||||
|  | import reactor.core.publisher.Mono; | ||||||
|  |  | ||||||
|  | @TestInstance(Lifecycle.PER_CLASS) | ||||||
|  | class InteractionCheckTest { | ||||||
|  |      | ||||||
|  |     @Test | ||||||
|  |     void testInteractionCheck() { | ||||||
|  |         ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("test"); | ||||||
|  |  | ||||||
|  |         LibraryWrapper wrapper = new Discord4JWrapper(null); | ||||||
|  |         Marinara marinara = Marinara.load(wrapper); | ||||||
|  |         marinara.getCheckContainer().addAllMethods(new TestInteractionCheck()); | ||||||
|  |         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||||
|  |         wrapper.handleInteraction(buttonEventMock); | ||||||
|  |  | ||||||
|  |         assertTrue(TestInteractionCheck.preExecuted); | ||||||
|  |         assertTrue(TestInteractionCheck.postExecuted); | ||||||
|  |         assertTrue(TestButton.didRun); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     void testPermissionCheck() { | ||||||
|  |         Member memberMock = mock(); | ||||||
|  |         Interaction interactionMock = mock(); | ||||||
|  |  | ||||||
|  |         when(memberMock.getBasePermissions()).thenReturn(Mono.just(PermissionSet.none())); | ||||||
|  |  | ||||||
|  |         when(interactionMock.getMember()).thenReturn(Optional.of(memberMock)); | ||||||
|  |  | ||||||
|  |         ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("permissionCheck", interactionMock); | ||||||
|  |  | ||||||
|  |         LibraryWrapper wrapper = new Discord4JWrapper(null); | ||||||
|  |         Marinara marinara = Marinara.load(wrapper); | ||||||
|  |         marinara.getCheckContainer().addAllMethods(new PermissionCheck()); | ||||||
|  |         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||||
|  |  | ||||||
|  |         wrapper.handleInteraction(buttonEventMock); | ||||||
|  |         assertFalse(TestButton.didPermRun); | ||||||
|  |         TestButton.didPermRun = false; | ||||||
|  |          | ||||||
|  |         when(memberMock.getBasePermissions()).thenReturn(Mono.just(PermissionSet.of(Permission.ATTACH_FILES))); | ||||||
|  |  | ||||||
|  |         wrapper.handleInteraction(buttonEventMock); | ||||||
|  |         assertTrue(TestButton.didPermRun); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,75 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  |  | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | import org.junit.jupiter.api.AfterAll; | ||||||
|  | import org.junit.jupiter.api.BeforeAll; | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | import org.junit.jupiter.api.TestInstance; | ||||||
|  | import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||||
|  |  | ||||||
|  | import discord4j.core.DiscordClient; | ||||||
|  | import discord4j.core.GatewayDiscordClient; | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOptionValue; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandOption.Type; | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||||
|  |  | ||||||
|  | @TestInstance(Lifecycle.PER_CLASS) | ||||||
|  | class SlashCommandTest { | ||||||
|  |  | ||||||
|  |     private static String DISCORD_TOKEN = System.getenv("DISCORD_TEST_TOKEN"); | ||||||
|  |     GatewayDiscordClient client; | ||||||
|  |  | ||||||
|  |     @BeforeAll | ||||||
|  |     void setUp() { | ||||||
|  |         client = DiscordClient.create(DISCORD_TOKEN).login().block(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @AfterAll | ||||||
|  |     void tearDown() { | ||||||
|  |         client.logout().block(); | ||||||
|  |         client = null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     void testSlashCommand() { | ||||||
|  |         Marinara marinara = Marinara.load(new Discord4JWrapper(client)); | ||||||
|  |         marinara.getInteractionContainer().addAllMethods(new TestCommand()); | ||||||
|  |         marinara.registerCommands(); | ||||||
|  |         System.out.println("Success!"); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Test | ||||||
|  |     void testSlashCommandExecution() { | ||||||
|  |         ApplicationCommandInteractionOption optionMock = mock(); | ||||||
|  |         ChatInputInteractionEvent eventMock = mock(); | ||||||
|  |  | ||||||
|  |         when(optionMock.getName()).thenReturn("foo"); | ||||||
|  |         when(optionMock.getType()).thenReturn(Type.STRING); | ||||||
|  |         when(optionMock.getValue()).thenReturn( | ||||||
|  |             Optional.of( | ||||||
|  |                 new ApplicationCommandInteractionOptionValue(null, null, Type.STRING.getValue(), "test", null) | ||||||
|  |                 )); | ||||||
|  |  | ||||||
|  |         when(eventMock.getCommandName()).thenReturn("test"); | ||||||
|  |         when(eventMock.getOptions()).thenReturn(Arrays.asList(optionMock)); | ||||||
|  |         when(eventMock.getOption("foo")).thenReturn(Optional.of(optionMock)); | ||||||
|  |          | ||||||
|  |         LibraryWrapper wrapper = new Discord4JWrapper(client); | ||||||
|  |         Marinara marinara = Marinara.load(wrapper); | ||||||
|  |         marinara.getInteractionContainer().addAllMethods(new TestCommand()); | ||||||
|  |  | ||||||
|  |         wrapper.handleInteraction(eventMock); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |      | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandOptionChoiceData; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.annotation.AutoComplete; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||||
|  |  | ||||||
|  | public class TestAutocomplete implements InteractionHandler { | ||||||
|  |  | ||||||
|  |     @SlashCommand( | ||||||
|  |         name = "test", | ||||||
|  |         options = @SlashCommandOption( | ||||||
|  |                 name = "foo", | ||||||
|  |                 type = SlashCommandOptionType.STRING, | ||||||
|  |                 autocompletes = @AutoComplete("testAuto") | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     public void exec(ChatInputInteractionEvent context) { | ||||||
|  |         // Not executed just there for autocomplete to work | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @AutoComplete("testAuto") | ||||||
|  |     public ApplicationCommandOptionChoiceData[] autocomplete(ChatInputAutoCompleteEvent context, String value) { | ||||||
|  |         System.out.println("Success!"); | ||||||
|  |         assertEquals("test", value); | ||||||
|  |         return new ApplicationCommandOptionChoiceData[]{ | ||||||
|  |             ApplicationCommandOptionChoiceData.builder().name("TestValue").value("test").build() | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertNotNull; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||||
|  | import discord4j.core.object.entity.Guild; | ||||||
|  | import discord4j.core.object.entity.Member; | ||||||
|  | import discord4j.core.object.entity.Message; | ||||||
|  | import discord4j.core.object.entity.User; | ||||||
|  | import discord4j.core.object.entity.channel.MessageChannel; | ||||||
|  | import discord4j.rest.util.Permission; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.annotation.Button; | ||||||
|  | import net.tomatentum.marinara.test.discord4j.TestInteractionCheck.TestCheck; | ||||||
|  | import net.tomatentum.marinara.wrapper.discord4j.checks.PermissionCheck.HasPermission; | ||||||
|  |  | ||||||
|  | public class TestButton implements InteractionHandler { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public static boolean didRun = false; | ||||||
|  |     @Button("test") | ||||||
|  |     @TestCheck | ||||||
|  |     public void exec(ButtonInteractionEvent interaction, MessageChannel channel, Message message, Member member, User user, Guild server) { | ||||||
|  |         assertNotNull(interaction); | ||||||
|  |         assertNotNull(channel); | ||||||
|  |         assertNotNull(message); | ||||||
|  |         assertNotNull(member); | ||||||
|  |         assertNotNull(user); | ||||||
|  |         assertNotNull(server); | ||||||
|  |         didRun = true; | ||||||
|  |         System.out.println("Success!"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static boolean didPermRun = false; | ||||||
|  |  | ||||||
|  |     @Button("permissionCheck") | ||||||
|  |     @HasPermission({Permission.ATTACH_FILES}) | ||||||
|  |     public void exec(ButtonInteractionEvent interaction) { | ||||||
|  |         didPermRun = true; | ||||||
|  |         System.out.println("It worked!"); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.choice.ChoiceValueProvider; | ||||||
|  |  | ||||||
|  | public enum TestChoiceEnum implements ChoiceValueProvider<String> { | ||||||
|  |     TestValue("testValue"), | ||||||
|  |     FooBar("fooBar"), | ||||||
|  |     Spongebob("spongebob"); | ||||||
|  |  | ||||||
|  |     private String value; | ||||||
|  |  | ||||||
|  |     private TestChoiceEnum(String value) { | ||||||
|  |         this.value = value; | ||||||
|  |     } | ||||||
|  |     @Override | ||||||
|  |     public String getChoiceValue() { | ||||||
|  |         return value; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.CommandChoices; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||||
|  |  | ||||||
|  | public class TestCommand implements InteractionHandler { | ||||||
|  |     @SlashCommand( | ||||||
|  |         name = "test",  | ||||||
|  |         description = "testingen", | ||||||
|  |         serverIds = { | ||||||
|  |             1037753048602255440L | ||||||
|  |         }, | ||||||
|  |         options = { | ||||||
|  |             @SlashCommandOption( | ||||||
|  |                 name = "foo", | ||||||
|  |                 description = "foo bar is very fooby", | ||||||
|  |                 type = SlashCommandOptionType.STRING, | ||||||
|  |                 choices = @CommandChoices(cenum = TestChoiceEnum.class) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |         ) | ||||||
|  |     public void exec(ChatInputInteractionEvent event, String test) { | ||||||
|  |         assertEquals("test", test); | ||||||
|  |         System.out.println("Success!"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertNotNull; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.checks.InteractionCheck; | ||||||
|  |  | ||||||
|  | public class TestInteractionCheck implements InteractionCheck<TestInteractionCheck.TestCheck> { | ||||||
|  |  | ||||||
|  |     public static boolean preExecuted = false; | ||||||
|  |     public static boolean postExecuted = false; | ||||||
|  |  | ||||||
|  |     @Target({ElementType.METHOD}) | ||||||
|  |     @Retention(RetentionPolicy.RUNTIME) | ||||||
|  |     public static @interface TestCheck { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean preExec(Object context, TestCheck annotation) { | ||||||
|  |         assertNotNull(annotation); | ||||||
|  |         assertNotNull(context); | ||||||
|  |         preExecuted = true; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void postExec(Object context, TestCheck annotation) { | ||||||
|  |         assertNotNull(annotation); | ||||||
|  |         assertNotNull(context); | ||||||
|  |         postExecuted = true; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,55 @@ | |||||||
|  | package net.tomatentum.marinara.test.discord4j.mocks; | ||||||
|  |  | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  |  | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||||
|  | import discord4j.core.object.command.Interaction; | ||||||
|  | import discord4j.core.object.entity.Guild; | ||||||
|  | import discord4j.core.object.entity.Member; | ||||||
|  | import discord4j.core.object.entity.Message; | ||||||
|  | import discord4j.core.object.entity.User; | ||||||
|  | import discord4j.core.object.entity.channel.MessageChannel; | ||||||
|  | import reactor.core.publisher.Mono; | ||||||
|  |  | ||||||
|  | public class CommonMocks { | ||||||
|  |     public static Interaction getInteractionMock() { | ||||||
|  |         Interaction interaction = mock(Interaction.class); | ||||||
|  |         Message message = mock(Message.class); | ||||||
|  |         MessageChannel channel = mock(MessageChannel.class); | ||||||
|  |         Guild guild = mock(Guild.class); | ||||||
|  |         User user = mock(User.class); | ||||||
|  |         Member member = mock(Member.class); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         when(interaction.getMessage()).thenReturn(Optional.of(message)); | ||||||
|  |         when(interaction.getChannel()).thenReturn(Mono.just(channel)); | ||||||
|  |         when(interaction.getGuild()).thenReturn(Mono.just(guild)); | ||||||
|  |         when(interaction.getUser()).thenReturn(user); | ||||||
|  |         when(interaction.getMember()).thenReturn(Optional.of(member)); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         return interaction; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static ButtonInteractionEvent getButtonEventMock(String customId) { | ||||||
|  |         ButtonInteractionEvent buttonEventMock = mock(ButtonInteractionEvent.class); | ||||||
|  |  | ||||||
|  |         when(buttonEventMock.getCustomId()).thenReturn(customId); | ||||||
|  |         Interaction interactionMock = getInteractionMock(); | ||||||
|  |         when(buttonEventMock.getInteraction()).thenReturn(interactionMock); | ||||||
|  |         Optional<Message> message = interactionMock.getMessage(); | ||||||
|  |         when (buttonEventMock.getMessage()).thenReturn(message); | ||||||
|  |         return buttonEventMock; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static ButtonInteractionEvent getButtonEventMock(String customId, Interaction interaction) { | ||||||
|  |         ButtonInteractionEvent buttonEventMock = mock(ButtonInteractionEvent.class); | ||||||
|  |  | ||||||
|  |         when(buttonEventMock.getCustomId()).thenReturn(customId); | ||||||
|  |         when(buttonEventMock.getInteraction()).thenReturn(interaction); | ||||||
|  |         return buttonEventMock; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | # SLF4J's SimpleLogger configuration file | ||||||
|  | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. | ||||||
|  |  | ||||||
|  | # Default logging detail level for all instances of SimpleLogger. | ||||||
|  | # Must be one of ("trace", "debug", "info", "warn", or "error"). | ||||||
|  | # If not specified, defaults to "info". | ||||||
|  | org.slf4j.simpleLogger.defaultLogLevel=trace | ||||||
							
								
								
									
										37
									
								
								wrapper/javacord/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								wrapper/javacord/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | /* | ||||||
|  |  * This file was generated by the Gradle 'init' task. | ||||||
|  |  * | ||||||
|  |  * This generated file contains a sample Java library project to get you started. | ||||||
|  |  * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.8/userguide/building_java_projects.html in the Gradle documentation. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | plugins { | ||||||
|  |     // Apply the java-library plugin for API and implementation separation. | ||||||
|  |     `java-library` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     // Use JUnit Jupiter for testing. | ||||||
|  |     testImplementation(libs.junit.jupiter) | ||||||
|  |     testImplementation(libs.mockito) | ||||||
|  |  | ||||||
|  |     testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||||||
|  |     testImplementation(libs.slf4j.simple) | ||||||
|  |  | ||||||
|  |     implementation(libs.slf4j) | ||||||
|  |     implementation(libs.javacord) | ||||||
|  |     implementation(libs.geantyref) | ||||||
|  |     implementation(project(":lib")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply a specific Java toolchain to ease working on different environments. | ||||||
|  | java { | ||||||
|  |     toolchain { | ||||||
|  |         languageVersion = JavaLanguageVersion.of(23) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tasks.named<Test>("test") { | ||||||
|  |     // Use JUnit Platform for unit tests. | ||||||
|  |     useJUnitPlatform() | ||||||
|  | } | ||||||
| @@ -0,0 +1,112 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord; | ||||||
|  |  | ||||||
|  | import org.javacord.api.interaction.AutocompleteInteraction; | ||||||
|  | import org.javacord.api.interaction.ButtonInteraction; | ||||||
|  | import org.javacord.api.interaction.InteractionBase; | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteraction; | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionType; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.option.AutocompleteOptionData; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  |  | ||||||
|  | public class JavacordContextObjectProvider implements ContextObjectProvider { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object convertCommandOption(Object context, String optionName) { | ||||||
|  |         if (!(context instanceof SlashCommandInteraction)) | ||||||
|  |             return null; | ||||||
|  |         SlashCommandInteraction interaction = (SlashCommandInteraction) context; | ||||||
|  |         if (!interaction.getArguments().isEmpty()) | ||||||
|  |             return getOptionValue(interaction.getOptionByName(optionName).orElse(null)); | ||||||
|  |  | ||||||
|  |         SlashCommandInteractionOption subCommandOption = interaction.getOptions().getFirst(); | ||||||
|  |  | ||||||
|  |         if (!subCommandOption.getOptions().isEmpty()) | ||||||
|  |             subCommandOption = subCommandOption.getOptions().getFirst(); | ||||||
|  |  | ||||||
|  |         return getOptionValue(subCommandOption.getOptionByName(optionName).orElse(null)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Object getOptionValue(SlashCommandInteractionOption option) { | ||||||
|  |         SlashCommandOptionType type = getOptionType(option); | ||||||
|  |         switch (type) { | ||||||
|  |             case ATTACHMENT: | ||||||
|  |                 return option.getAttachmentValue().get(); | ||||||
|  |             case BOOLEAN: | ||||||
|  |                 return option.getBooleanValue().get(); | ||||||
|  |             case CHANNEL: | ||||||
|  |                 return option.getChannelValue().get(); | ||||||
|  |             case DECIMAL: | ||||||
|  |                 return option.getDecimalValue().get(); | ||||||
|  |             case LONG: | ||||||
|  |                 return option.getLongValue().get(); | ||||||
|  |             case MENTIONABLE: | ||||||
|  |                 return option.requestMentionableValue().get(); | ||||||
|  |             case ROLE: | ||||||
|  |                 return option.getRoleValue().get(); | ||||||
|  |             case STRING: | ||||||
|  |                 return option.getStringValue().get(); | ||||||
|  |             case USER: | ||||||
|  |                 return option.requestUserValue().get(); | ||||||
|  |             default: | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private SlashCommandOptionType getOptionType(SlashCommandInteractionOption option) { | ||||||
|  |         if (option.getAttachmentValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.ATTACHMENT; | ||||||
|  |         if (option.getBooleanValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.BOOLEAN; | ||||||
|  |         if (option.getChannelValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.CHANNEL; | ||||||
|  |         if (option.getDecimalValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.DECIMAL; | ||||||
|  |         if (option.getLongValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.LONG; | ||||||
|  |         if (option.requestMentionableValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.MENTIONABLE; | ||||||
|  |         if (option.getRoleValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.ROLE; | ||||||
|  |         if (option.getStringValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.STRING; | ||||||
|  |         if (option.requestUserValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.USER; | ||||||
|  |  | ||||||
|  |         return SlashCommandOptionType.UNKNOWN; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getComponentContextObject(Object context, Class<?> type) { | ||||||
|  |         ButtonInteraction button = (ButtonInteraction) context; | ||||||
|  |         switch (type.getName()) { | ||||||
|  |             case "org.javacord.api.entity.message.Message": | ||||||
|  |                 return button.getMessage(); | ||||||
|  |             default: | ||||||
|  |                 return getInteractionContextObject(context, type); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getInteractionContextObject(Object context, Class<?> type) { | ||||||
|  |         InteractionBase button = (InteractionBase) context; | ||||||
|  |         switch (type.getName()) { | ||||||
|  |             case "org.javacord.api.entity.channel.TextChannel": | ||||||
|  |                 return button.getChannel().orElse(null); | ||||||
|  |             case "org.javacord.api.entity.server.Server": | ||||||
|  |                 return button.getServer().orElse(null); | ||||||
|  |             case "org.javacord.api.entity.user.User": | ||||||
|  |                 return button.getUser(); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public AutocompleteOptionData getAutocompleteFocusedOption(Object context) { | ||||||
|  |         SlashCommandInteractionOption option = ((AutocompleteInteraction) context).getFocusedOption(); | ||||||
|  |         return new AutocompleteOptionData(option.getName(), getOptionValue(option)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,77 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import org.javacord.api.interaction.SlashCommand; | ||||||
|  | import org.javacord.api.interaction.SlashCommandBuilder; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOption; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionBuilder; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionChoice; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionChoiceBuilder; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionType; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.CommandConverter; | ||||||
|  |  | ||||||
|  | public class JavacordConverterSpec implements CommandConverter.Spec<SlashCommandBuilder, SlashCommandOption, SlashCommandOptionChoice> { | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public SlashCommandBuilder convertCommand(RootCommandIdentifier rootIdentifier, List<SlashCommandOption> options) { | ||||||
|  | 		return SlashCommand.with(rootIdentifier.name(), rootIdentifier.description(), options); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public SlashCommandOption convertSubCommandGroup(InteractionIdentifier identifier, | ||||||
|  | 			List<SlashCommandOption> subCommands) { | ||||||
|  | 		return SlashCommandOption.createWithOptions( | ||||||
|  | 			SlashCommandOptionType.SUB_COMMAND_GROUP,  | ||||||
|  | 			identifier.name(),  | ||||||
|  | 			identifier.description(),  | ||||||
|  | 			subCommands); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public SlashCommandOption convertSubCommand(InteractionIdentifier identifier, List<SlashCommandOption> options) { | ||||||
|  | 		return SlashCommandOption.createWithOptions( | ||||||
|  |             SlashCommandOptionType.SUB_COMMAND,  | ||||||
|  |             identifier.name(),  | ||||||
|  |             identifier.description(),  | ||||||
|  |             options); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public SlashCommandOption convertOption( | ||||||
|  | 			net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption option, | ||||||
|  | 			List<SlashCommandOptionChoice> choices) { | ||||||
|  | 		SlashCommandOptionType type = SlashCommandOptionType.fromValue(option.type().getValue()); | ||||||
|  | 		return new SlashCommandOptionBuilder() | ||||||
|  |             .setType(type) | ||||||
|  |             .setName(option.name()) | ||||||
|  |             .setDescription(option.description()) | ||||||
|  |             .setRequired(option.required()) | ||||||
|  |             .setAutocompletable(option.autocompletes().length > 0) | ||||||
|  | 			.setMinLength((long) option.range().min()) | ||||||
|  | 			.setDecimalMinValue(option.range().min()) | ||||||
|  | 			.setMaxLength((long) option.range().max()) | ||||||
|  | 			.setDecimalMaxValue(option.range().max()) | ||||||
|  |             .setChoices(choices) | ||||||
|  | 			.build(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public SlashCommandOptionChoice convertChoice( | ||||||
|  | 			net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice choice) { | ||||||
|  | 		SlashCommandOptionChoiceBuilder builder = new SlashCommandOptionChoiceBuilder().setName(choice.name()); | ||||||
|  | 		if (choice.longValue() != Long.MAX_VALUE) | ||||||
|  | 			builder.setValue(choice.longValue()); | ||||||
|  | 		/* | ||||||
|  | 		not yet available | ||||||
|  | 		if (choice.doubleValue() != Double.MAX_VALUE) | ||||||
|  | 			builder.setValue(choice.doubleValue()); | ||||||
|  | 		*/ | ||||||
|  | 		if (!choice.stringValue().isEmpty()) | ||||||
|  | 			builder.setValue(choice.stringValue()); | ||||||
|  | 		return builder.build(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord; | ||||||
|  |  | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import org.javacord.api.DiscordApi; | ||||||
|  | import org.javacord.api.interaction.SlashCommandBuilder; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.wrapper.CommandRegisterer; | ||||||
|  | import net.tomatentum.marinara.wrapper.ServerCommandList; | ||||||
|  |  | ||||||
|  | public class JavacordRegistererStrategy implements CommandRegisterer.Strategy<SlashCommandBuilder> { | ||||||
|  |  | ||||||
|  |     private DiscordApi api; | ||||||
|  |  | ||||||
|  |     public JavacordRegistererStrategy(DiscordApi api) { | ||||||
|  |         this.api = api; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void registerServer(ServerCommandList<SlashCommandBuilder> commands) { | ||||||
|  |         api.bulkOverwriteServerApplicationCommands(commands.serverId(), commands); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void registerGlobal(Set<SlashCommandBuilder> defs) { | ||||||
|  |         api.bulkOverwriteGlobalApplicationCommands(defs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,70 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import org.javacord.api.DiscordApi; | ||||||
|  | import org.javacord.api.interaction.AutocompleteInteraction; | ||||||
|  | import org.javacord.api.interaction.SlashCommandBuilder; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionChoice; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.wrapper.CommandConverter; | ||||||
|  | import net.tomatentum.marinara.wrapper.CommandRegisterer; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.javacord.identifierconverter.AutocompleteIdentifierConverter; | ||||||
|  | import net.tomatentum.marinara.wrapper.javacord.identifierconverter.ButtonIdentifierConverter; | ||||||
|  | import net.tomatentum.marinara.wrapper.javacord.identifierconverter.SlashCommandIdentifierConverter; | ||||||
|  |  | ||||||
|  | public class JavacordWrapper extends LibraryWrapper { | ||||||
|  |  | ||||||
|  |     private JavacordContextObjectProvider contextObjectProvider; | ||||||
|  |     private CommandRegisterer<SlashCommandBuilder> commandRegisterer; | ||||||
|  |      | ||||||
|  |     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public JavacordWrapper(DiscordApi api) { | ||||||
|  |         this.contextObjectProvider = new JavacordContextObjectProvider(); | ||||||
|  |         var converter = CommandConverter.of(new JavacordConverterSpec()); | ||||||
|  |  | ||||||
|  |         if (api != null) { | ||||||
|  |             this.commandRegisterer = CommandRegisterer.of(new JavacordRegistererStrategy(api), converter); | ||||||
|  |             api.addInteractionCreateListener(e -> handleInteraction(e.getInteraction())); | ||||||
|  |         }else | ||||||
|  |             logger.warn("DiscordApi was null so no Events were subscribed to."); | ||||||
|  |         logger.info("Javacord wrapper loaded!"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public CommandRegisterer<?> getRegisterer() { | ||||||
|  |         return this.commandRegisterer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public IdentifierProvider createIdentifierProvider() { | ||||||
|  |         return IdentifierProvider.of( | ||||||
|  |             new SlashCommandIdentifierConverter(), | ||||||
|  |             new AutocompleteIdentifierConverter(), | ||||||
|  |             new ButtonIdentifierConverter() | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public ContextObjectProvider getContextObjectProvider() { | ||||||
|  |         return contextObjectProvider; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void respondAutocomplete(Object context, List<Object> options) { | ||||||
|  |         if (context instanceof AutocompleteInteraction interaction) { | ||||||
|  |             List<SlashCommandOptionChoice> choices = options.stream() | ||||||
|  |                 .filter(SlashCommandOptionChoice.class::isInstance) | ||||||
|  |                 .map(o -> (SlashCommandOptionChoice)o) | ||||||
|  |                 .toList(); | ||||||
|  |             interaction.respondWithChoices(choices); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord.checks; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | import org.javacord.api.entity.permission.PermissionType; | ||||||
|  | import org.javacord.api.entity.server.Server; | ||||||
|  | import org.javacord.api.interaction.InteractionBase; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.checks.InteractionCheck; | ||||||
|  |  | ||||||
|  | public class PermissionCheck implements InteractionCheck<PermissionCheck.HasPermission> { | ||||||
|  |  | ||||||
|  |     @Retention(RetentionPolicy.RUNTIME) | ||||||
|  |     @Target(ElementType.METHOD) | ||||||
|  |     public static @interface HasPermission { | ||||||
|  |         public PermissionType[] value(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean preExec(Object context, HasPermission annotation) { | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'preExec'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean preExec(InteractionBase context, HasPermission annotation) { | ||||||
|  |         Optional<Server> server = context.getServer(); | ||||||
|  |         if (!server.isPresent()) | ||||||
|  |             return false; | ||||||
|  |  | ||||||
|  |        return server.get().hasPermissions(context.getUser(), annotation.value()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void postExec(Object context, HasPermission annotation) { | ||||||
|  |          | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord.identifierconverter; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import org.javacord.api.interaction.AutocompleteInteraction; | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  |  | ||||||
|  | public class AutocompleteIdentifierConverter implements IdentifierProvider.Converter<AutocompleteInteraction> { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier convert(AutocompleteInteraction context) { | ||||||
|  |         InteractionIdentifier last = InteractionIdentifier.builder() | ||||||
|  |             .type(InteractionType.AUTOCOMPLETE) | ||||||
|  |             .name(context.getCommandName()) | ||||||
|  |             .build(); | ||||||
|  |  | ||||||
|  |         List<SlashCommandInteractionOption> options = context.getOptions(); | ||||||
|  |         if (!options.isEmpty()) { | ||||||
|  |             last = InteractionIdentifier.builder() | ||||||
|  |                 .type(InteractionType.AUTOCOMPLETE) | ||||||
|  |                 .name(options.getFirst().getName()) | ||||||
|  |                 .parent(last) | ||||||
|  |                 .build(); | ||||||
|  |  | ||||||
|  |             List<SlashCommandInteractionOption> subOptions = context.getOptions().getFirst().getOptions(); | ||||||
|  |             if (!subOptions.isEmpty()) | ||||||
|  |                 last = InteractionIdentifier.builder() | ||||||
|  |                     .type(InteractionType.AUTOCOMPLETE) | ||||||
|  |                     .name(subOptions.getFirst().getName()) | ||||||
|  |                     .parent(last) | ||||||
|  |                     .build(); | ||||||
|  |         } | ||||||
|  |         return last; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord.identifierconverter; | ||||||
|  |  | ||||||
|  | import org.javacord.api.interaction.ButtonInteraction; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  |  | ||||||
|  | public class ButtonIdentifierConverter implements IdentifierProvider.Converter<ButtonInteraction> { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier convert(ButtonInteraction context) { | ||||||
|  |         return InteractionIdentifier.builder().name(context.getCustomId()).type(InteractionType.BUTTON).build(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord.identifierconverter; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteraction; | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||||
|  | import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||||
|  |  | ||||||
|  | public class SlashCommandIdentifierConverter implements IdentifierProvider.Converter<SlashCommandInteraction> { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionIdentifier convert(SlashCommandInteraction context) { | ||||||
|  |         InteractionIdentifier last = InteractionIdentifier.builder() | ||||||
|  |             .type(InteractionType.COMMAND) | ||||||
|  |             .name(context.getCommandName()) | ||||||
|  |             .build(); | ||||||
|  |  | ||||||
|  |         List<SlashCommandInteractionOption> options = context.getOptions(); | ||||||
|  |         if (!options.isEmpty()) { | ||||||
|  |             last = InteractionIdentifier.builder() | ||||||
|  |                 .type(InteractionType.COMMAND) | ||||||
|  |                 .name(options.getFirst().getName()) | ||||||
|  |                 .parent(last) | ||||||
|  |                 .build(); | ||||||
|  |                  | ||||||
|  |             List<SlashCommandInteractionOption> subOptions = context.getOptions().getFirst().getOptions(); | ||||||
|  |             if (!subOptions.isEmpty()) | ||||||
|  |                 last = InteractionIdentifier.builder() | ||||||
|  |                     .type(InteractionType.COMMAND) | ||||||
|  |                     .name(subOptions.getFirst().getName()) | ||||||
|  |                     .parent(last) | ||||||
|  |                     .build(); | ||||||
|  |         } | ||||||
|  |         return last; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | package net.tomatentum.marinara.test.javacord; | ||||||
|  |  | ||||||
|  | import static org.mockito.ArgumentMatchers.any; | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.verify; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  |  | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | import org.javacord.api.interaction.AutocompleteInteraction; | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||||
|  |  | ||||||
|  | class AutoCompleteTest { | ||||||
|  |      | ||||||
|  |     @Test | ||||||
|  |     void testAutocomplete() { | ||||||
|  |  | ||||||
|  |         SlashCommandInteractionOption optionMock = mock(); | ||||||
|  |         AutocompleteInteraction autocompleteInteractionMock = mock(); | ||||||
|  |  | ||||||
|  |         when(optionMock.getName()).thenReturn("foo"); | ||||||
|  |         when(optionMock.getStringValue()).thenReturn(Optional.of("test")); | ||||||
|  |  | ||||||
|  |         when(autocompleteInteractionMock.getCommandName()).thenReturn("test"); | ||||||
|  |         when(autocompleteInteractionMock.getFocusedOption()).thenReturn(optionMock); | ||||||
|  |         when(autocompleteInteractionMock.getOptions()).thenReturn(Collections.emptyList()); | ||||||
|  |  | ||||||
|  |         LibraryWrapper wrapper = new JavacordWrapper(null); //null okay as we don't use the discord API in this test. | ||||||
|  |         Marinara marinara = Marinara.load(wrapper); | ||||||
|  |         marinara.getInteractionContainer().addAllMethods(new TestAutocomplete()); | ||||||
|  |         wrapper.handleInteraction(autocompleteInteractionMock); | ||||||
|  |         verify(autocompleteInteractionMock).respondWithChoices(any()); | ||||||
|  |     } | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user