Compare commits

...

122 Commits

Author SHA1 Message Date
070319853a
refactor(core): restructure MethodProcessors
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 4m46s
Test / Gradle-Test (push) Successful in 4m57s
2025-04-07 00:35:06 +02:00
60ead419e2
refactor(core): consistency 2025-04-06 13:31:31 +02:00
0b7b607a23
refactor(wrapper): improve Converter structure
All checks were successful
github-mirror / push-github (push) Successful in 1m38s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 47s
2025-04-06 13:14:22 +02:00
991d1c047b
feat(core): add InteractionProcessors and InteractionExecutors and improve Autocomplete Interaction
All checks were successful
github-mirror / push-github (push) Successful in 1m45s
Build / Gradle-Build (push) Successful in 37s
Test / Gradle-Test (push) Successful in 46s
2025-04-06 00:04:17 +02:00
ec17952375
feat(build): add prefix to archive jar
All checks were successful
github-mirror / push-github (push) Successful in 1m37s
Build / Gradle-Build (push) Successful in 35s
Test / Gradle-Test (push) Successful in 46s
2025-04-01 10:18:37 +02:00
0114cffcbd
refactor(method): make use of optionals in ReflectedMethodFactory
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 39s
Test / Gradle-Test (push) Successful in 50s
2025-03-31 10:50:36 +02:00
450f1fdaa1
refactor(autocomplete): implement autocompleteRefs and remove SlashCommand annotation on Autocomplete Method
All checks were successful
github-mirror / push-github (push) Successful in 1m41s
Build / Gradle-Build (push) Successful in 34s
Test / Gradle-Test (push) Successful in 48s
2025-03-31 10:36:49 +02:00
92540576df
fix(registry): add null check to InteractionIdentifier#equals 2025-03-31 10:30:24 +02:00
8a3cde52fd
refactor(interaction): implement Factory pattern for all Interaction- and ReflectedMethods
All checks were successful
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 43s
Test / Gradle-Test (push) Successful in 57s
2025-03-29 18:49:26 +01:00
8495659364
fix(registry): fix logic error in InteractionEntry identifier checking and fix InteractionIdentifier#equals to actually recursively check parent identifiers
Some checks failed
github-mirror / push-github (push) Successful in 1m44s
Build / Gradle-Build (push) Successful in 38s
Test / Gradle-Test (push) Failing after 46s
2025-03-29 18:37:51 +01:00
0973016a74
refactor(interaction): seperate InteractionMethod to ReflectedMethod
Some checks failed
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 31s
Test / Gradle-Test (push) Failing after 43s
2025-03-26 22:22:39 +01:00
0590789359
fix: compile error
Some checks failed
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Failing after 44s
2025-03-26 19:46:44 +01:00
2647a1f0b4
refactor(registry): outsource entry discovery to seperate method
Some checks failed
github-mirror / push-github (push) Successful in 1m39s
Build / Gradle-Build (push) Failing after 37s
Test / Gradle-Test (push) Failing after 34s
2025-03-26 19:26:57 +01:00
a3c5eb62ac Merge pull request 'add option ranges' (#20) from feat/option-ranges into dev
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Publish / Gradle-Publish (push) Successful in 43s
Test / Gradle-Test (push) Successful in 47s
Reviewed-on: #20
2025-03-22 18:42:28 +00:00
996f854ff7
feat(command): add wrapper implementations of option ranges
All checks were successful
github-mirror / push-github (push) Successful in 1m40s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Successful in 47s
2025-03-22 12:33:21 +01:00
d2eec8b07c
feat(command): add option ranges to options 2025-03-22 12:31:25 +01:00
caa2ee7089
refactor(command): move both choices vars to different annotation
All checks were successful
github-mirror / push-github (push) Successful in 1m36s
Build / Gradle-Build (push) Successful in 31s
Publish / Gradle-Publish (push) Successful in 36s
Test / Gradle-Test (push) Successful in 47s
2025-03-18 09:33:53 +01:00
2e5979e6e4 Merge pull request 'migrate to slf4j' (#18) from migrate/slf4j into dev
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 1m45s
Publish / Gradle-Publish (push) Successful in 1m32s
Test / Gradle-Test (push) Successful in 1m38s
Reviewed-on: #18
2025-03-17 19:34:26 +00:00
ab1eb74e85
fix(logging) implement getQualifiedCallerName
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 35s
Test / Gradle-Test (push) Successful in 48s
2025-03-17 14:36:43 +01:00
a5737b9eaa
feat(logging): add Fallback logging
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 31s
Test / Gradle-Test (push) Successful in 47s
2025-03-17 14:13:21 +01:00
faca21724c
fix(logging): fix wrong getClass method. 2025-03-17 14:13:00 +01:00
4c5e28b679
feat(logging): replace log4j dependency with slf4j and replace imports
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 38s
Test / Gradle-Test (push) Successful in 51s
2025-03-17 10:52:24 +01:00
33f355e6ea Merge pull request 'improve wrapper and general structure' (#16) from improve/structure into dev
All checks were successful
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 36s
Publish / Gradle-Publish (push) Successful in 32s
Test / Gradle-Test (push) Successful in 48s
Reviewed-on: #16
No new features
2025-03-17 08:26:43 +00:00
d32ac62b4a
fix(autocomplete): fix autocomplete using component context objects
All checks were successful
github-mirror / push-github (push) Successful in 1m46s
Build / Gradle-Build (push) Successful in 31s
Test / Gradle-Test (push) Successful in 59s
2025-03-17 08:37:11 +01:00
e7c35d9308
refactor(core): remove redundant distinct method and its uses
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 46s
2025-03-17 00:16:26 +01:00
d4a91f3251
fix(core): fix wrong equality method and refactor a bit 2025-03-17 00:15:10 +01:00
bce4ce7812
fix(wrapper): add condition for CommandRegisterer if api is null 2025-03-16 23:35:39 +01:00
bae077654e
refactor(Discord4J): implement CommandRegisterer refactor
Some checks failed
github-mirror / push-github (push) Successful in 1m44s
Build / Gradle-Build (push) Successful in 55s
Test / Gradle-Test (push) Failing after 41s
2025-03-16 17:16:26 +01:00
203498de68
refactor(javacord): implement CommandRegisterer refactor 2025-03-16 17:07:14 +01:00
24df1731da
refactor(command): add CommandRegisterer 2025-03-16 17:06:37 +01:00
e3fc10a1ce
fix(util): ObjectListAggregator syntax 2025-03-16 02:48:24 +01:00
78cacb7eb6
feat(util): add ObjectListAggregator 2025-03-16 02:47:16 +01:00
7287d44645
refactor(util): add multiple key support to ObjectAggregator 2025-03-16 02:46:55 +01:00
630c8ddee5
feat(register): add convenience getter 2025-03-16 01:49:24 +01:00
4e27e6ce56
feat(struct): introduce ObjectAggregator 2025-03-16 01:36:23 +01:00
432cf78a2e
add IdentifierProvider and wrapper implementations
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Successful in 48s
2025-03-05 21:44:12 +01:00
f940f48566
move CommandConverter to wrapper package
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 31s
Test / Gradle-Test (push) Successful in 46s
2025-03-04 11:16:44 +01:00
f4ee258eb1
remove missing parent() call in SlashCommandDefinition#getSubCommands()
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 53s
Test / Gradle-Test (push) Successful in 52s
2025-03-03 23:40:06 +01:00
56b668851b
add CommandConverter logic and wrapper implementations
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 37s
Test / Gradle-Test (push) Successful in 48s
2025-03-03 23:32:25 +01:00
823402e0cd
fix base getSubCommands method 2025-03-03 23:32:07 +01:00
91b1df8d5b
replace ExecutableCommandDefinition with InteractionIdentifiers
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 42s
Test / Gradle-Test (push) Successful in 45s
2025-03-03 12:42:04 +01:00
76d7e9ef4f Merge branch 'dev' into improve/structure
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 1m20s
Test / Gradle-Test (push) Successful in 49s
2025-03-01 22:59:40 +00:00
236c584da8
move Choice classes
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 10s
Test / Gradle-Test (push) Successful in 20s
2025-02-20 09:10:28 +01:00
f6db113deb
add CommandConverter stub
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 10s
Test / Gradle-Test (push) Successful in 21s
2025-02-20 00:41:15 +01:00
9058629af5
remove redundant getFullSlashCommand
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 11s
Test / Gradle-Test (push) Successful in 21s
2025-02-19 21:50:54 +01:00
8f14b0feb9
split up getSubCommands 2025-02-19 21:49:12 +01:00
3e02e7b85b Merge pull request 'Migrate to Mockito' (#15) from migrate/mockito into dev
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 11s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 22s
Reviewed-on: #15
2025-02-19 19:22:39 +00:00
ba560cb909
remove deprecated manual mocks
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 10s
Test / Gradle-Test (push) Successful in 21s
2025-02-19 15:34:14 +01:00
6cdca411c6
migrate SlashCommandTest 2025-02-19 15:33:21 +01:00
3a39b268c1
migrate InteractionCheckTest 2025-02-19 14:39:48 +01:00
ca0ab13316
migrate ButtonTest to mockito
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 21s
2025-02-19 14:10:45 +01:00
ded81983e3
migrate AutocompleteTest and add mockito ofc 2025-02-19 14:00:01 +01:00
92704ca230
add correct null handling for api objects 2025-02-19 13:56:45 +01:00
5d8f737481 Merge pull request 'add Discord4J Wrapper' (#14) from wrapper/d4j into dev
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 10s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 21s
Reviewed-on: #14
2025-02-19 12:43:38 +00:00
8943d6d4a4
add tests
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 18s
Test / Gradle-Test (push) Successful in 21s
2025-02-19 10:50:30 +01:00
842fcfe5ac
add PermissionCheck 2025-02-19 10:50:19 +01:00
858fab5e32
fix: Message ContextObject returning Optional 2025-02-19 09:02:52 +01:00
20471fefea
fix: logging causing NullPointerException 2025-02-19 09:01:42 +01:00
8d27ec28db
fix: remove Test Line, allow api to be null, fix getCommandDefinition to also allow ChatInputAutoCompleteEvent 2025-02-18 11:21:32 +01:00
f81602f5db
restructure and add discord4j test stub
Some checks failed
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 9s
Test / Gradle-Test (push) Failing after 10s
2025-02-17 23:59:08 +01:00
dbad157058
adjust actions to add new wrapper
Some checks failed
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 9s
Test / Gradle-Test (push) Failing after 13s
2025-02-17 20:24:40 +01:00
ad19ed6ada
add First prototype of Discord4J wrapper
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 21s
Test / Gradle-Test (push) Successful in 14s
2025-02-17 20:13:31 +01:00
ca822909e3
fix: consistency 2025-02-17 19:55:03 +01:00
caeaec1926
add: fromValue method to SlashCommandOptionType 2025-02-17 19:45:18 +01:00
f4dbdc302d
fix: improve consistency 2025-02-17 19:44:52 +01:00
fd87431d51
Merge remote-tracking branch 'origin/dev' into wrapper/d4j 2025-02-17 13:06:27 +01:00
3b65784770 Merge pull request 'Add Choices and Autocomplete' (#8) from feat/choices into dev
All checks were successful
github-mirror / push-github (push) Successful in 30s
Build / Gradle-Build (push) Successful in 14s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 14s
Reviewed-on: #8
Double choices missing because PR will not get merged since Javacord seized development
2025-02-17 10:14:37 +00:00
b4af922ac2
change: getInteractionType check changed from class based to context based check
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 16s
Test / Gradle-Test (push) Successful in 14s
2025-02-17 09:15:54 +01:00
1146fa1cc6
Merge branch 'dev' into feat/choices
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 34s
Test / Gradle-Test (push) Successful in 41s
2024-12-20 19:30:35 +01:00
9d88ca902d
fix NoSuchElement case on command with no options
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 35s
Publish / Gradle-Publish (push) Successful in 36s
Test / Gradle-Test (push) Successful in 43s
2024-12-20 19:27:36 +01:00
e9b5610f3c Merge pull request 'add Logging to various locations' (#11) from feat/logging into dev
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 37s
Publish / Gradle-Publish (push) Successful in 33s
Test / Gradle-Test (push) Successful in 40s
Reviewed-on: #11
2024-12-20 17:51:24 +00:00
8e872e7ada
Merge branch 'dev' into feat/logging
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 40s
2024-12-20 17:05:18 +01:00
43c5946227
Merge branch 'dev' into feat/choices
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 40s
2024-12-20 17:04:13 +01:00
71c910ab93
fix equality check of ExecutableSlashCommand
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Publish / Gradle-Publish (push) Successful in 32s
Test / Gradle-Test (push) Successful in 39s
2024-12-20 17:03:35 +01:00
c5a7f3665e
Merge branch 'dev' into feat/choices
All checks were successful
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 40s
2024-12-20 14:56:35 +01:00
3baf1eda9a
log the result of an InteractionCheck
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 38s
2024-12-20 00:44:48 +01:00
628abb4239
fix order and wording
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 42s
2024-12-20 00:32:20 +01:00
9a89544a9e
Merge branch 'dev' into feat/logging
All checks were successful
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 31s
Test / Gradle-Test (push) Successful in 41s
2024-12-19 22:11:21 +01:00
455fc4955f
fix ExecutableSlashCommandDefinition toString method with all various checks
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Publish / Gradle-Publish (push) Successful in 33s
Test / Gradle-Test (push) Successful in 39s
2024-12-19 22:11:06 +01:00
b72c55fc6e
Merge branch 'dev' into feat/logging
Some checks failed
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Failing after 39s
2024-12-19 21:23:48 +01:00
3691434f66
fix toString method of ExecutableSlashCommandDefinition and added subCommandGroup display
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Publish / Gradle-Publish (push) Successful in 33s
Test / Gradle-Test (push) Successful in 39s
2024-12-19 21:23:24 +01:00
74d55d81ca
add remaining logging in Javacord wrapper and exceptions
Some checks failed
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Failing after 41s
2024-12-19 21:12:36 +01:00
404f221ccf
added Logging for InteractionChecks
Some checks failed
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Failing after 40s
2024-12-19 21:07:42 +01:00
7249c99b69
add Logging to Parsers 2024-12-19 20:38:04 +01:00
b764972eba
make use of new helper method in InteractionMethod 2024-12-19 20:15:22 +01:00
83ee4b1efa
add method name helper method 2024-12-19 20:04:53 +01:00
6b86e9ff87
add initialization Logging 2024-12-19 19:41:49 +01:00
7a40aebd6d
add Interactionmethod logging 2024-12-19 19:41:37 +01:00
76ab779ab2
fix position of Logging in InteractionRegistry
All checks were successful
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 35s
Test / Gradle-Test (push) Successful in 47s
2024-12-19 18:36:51 +01:00
3d19fae6b8
fix null issue in InteractionRegistry
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Successful in 44s
2024-12-19 18:34:00 +01:00
1ecbc563a6
add Logging in InteractionRegistry
Some checks failed
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 34s
Test / Gradle-Test (push) Failing after 44s
2024-12-19 18:07:33 +01:00
bf0022775d
add helper LoggerUtil method 2024-12-19 13:30:36 +01:00
a17f5e826f
add LoggerUtil to decide which Logger to get
All checks were successful
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 35s
Test / Gradle-Test (push) Successful in 39s
2024-12-19 12:28:45 +01:00
3de0f32074
change to just javacord api dependency 2024-12-19 12:28:27 +01:00
aaf4f3297a
add autocomplete option toggle, add double value for non javacord wrappers, rename OptionChoices to match with discords naming and general cleanup
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 31s
Test / Gradle-Test (push) Successful in 39s
2024-12-16 13:03:11 +01:00
445190db89
added Tests for choices 2024-12-16 12:49:51 +01:00
9d3a6b8b85
add Autocomplete Test
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Successful in 39s
2024-12-15 23:15:37 +01:00
1cb6cd0e05
clean up code and switch to request instead of getting from cache 2024-12-15 23:15:29 +01:00
a5e1230fc6
fix issues with ExecutableSlashCommandDefinition equals check 2024-12-15 23:13:57 +01:00
432db43bf5
add remaining parts of AutocompleteInteraction
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Successful in 40s
2024-12-15 15:09:09 +01:00
7a2c15d877
create seperate class for ContextObjectProviders and renamed some context parameters from parameter to context.
First parts of AutocompleteInteraction added
2024-12-15 15:08:34 +01:00
f32c7045a1
remove unnecessary getClass call fixing test
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 36s
Test / Gradle-Test (push) Successful in 40s
2024-12-14 12:32:03 +01:00
69b27e4554
add prototype choices implementation
Some checks failed
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 50s
Test / Gradle-Test (push) Failing after 41s
2024-12-13 10:50:30 +01:00
d8504a7cde Merge pull request 'add Checks system' (#6) from feat/checks into dev
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 11s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 16s
Reviewed-on: #6
2024-12-05 07:51:36 +00:00
bef34ee548 fix null issue
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 16s
2024-12-03 20:20:57 +01:00
29bb7e667e
add PermissionCHeck test
Some checks failed
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 11s
Test / Gradle-Test (push) Failing after 16s
2024-12-02 21:21:00 +01:00
94da2a0e3c
change logic to always give objects the highest value 2024-12-02 21:20:49 +01:00
7f47130461
add missing annotation annotations 2024-12-02 21:20:26 +01:00
83a3efd4b8
add canRun check which i forgor 2024-12-02 21:19:58 +01:00
aefd8a51a0
change wrong annotation class usage 2024-12-02 21:19:42 +01:00
4332592dfa
add Javacord PermissionCheck
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 17s
2024-12-02 13:16:28 +01:00
c363ab9744 add ability to return false to cancel execution in pre Checks and remove return type on post checks
All checks were successful
github-mirror / push-github (push) Successful in 7s
Build / Gradle-Build (push) Successful in 14s
Test / Gradle-Test (push) Successful in 21s
2024-12-01 13:06:42 +01:00
33392b02fb
add InteractionCheck test
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 13s
Test / Gradle-Test (push) Successful in 15s
2024-11-29 21:41:46 +01:00
b7333c2e5e
fix problem with multiple method overrides in generic types. 2024-11-29 21:41:18 +01:00
239e921e6f
change Annotation#getClass to Annotation#annotationType because it was not working as expected 2024-11-29 21:37:13 +01:00
6eb7fb723f
add geantyref to fix and simplify generic Type parsing.
Also switch to java 23 to avoid conflicts and issues.
2024-11-29 21:36:02 +01:00
659218682e
add context Object to check methods and create the ability to have a specific method for each type of context or one for all by using the superclass and casting yourself
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 17s
2024-11-29 18:17:33 +01:00
019ba8f552
add getMostSpecificMethod method to simplify Check method parsing.
This Method searches the The method that has the best matching parameters with the fewest inheritance levels as possible. Left sided priority
2024-11-29 18:15:47 +01:00
f89ae5e425
- add prototype Interactioncheck impementation.
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 16s
- refactor dependency injection to have all widely used dependencies in the Marinara class.
2024-11-28 10:32:48 +01:00
582e0f0bae
implement AnnotationParser system
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 13s
Test / Gradle-Test (push) Successful in 16s
2024-11-24 00:02:19 +01:00
0ea330d48b
move to seperate files because gitea does not have expressions yet
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 9s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 13s
2024-11-20 12:01:47 +01:00
c241f6b1fe
enable dev branch publishing
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 17s
Publish / Gradle-Publish (push) Successful in 6s
Test / Gradle-Test (push) Successful in 13s
2024-11-20 11:52:41 +01:00
106 changed files with 3737 additions and 5792 deletions

View File

@ -38,7 +38,10 @@ jobs:
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;
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

View 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

View File

@ -1,11 +1,11 @@
name: Test
name: Publish
on:
push:
branches: [master]
jobs:
Gradle-Test:
Gradle-Publish:
runs-on: ubuntu-latest
steps:
@ -34,7 +34,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Publish
- name: Publish Release
env:
GITEA_TOKEN: ${{ secrets.PUBLISH_PACKAGE_TOKEN }}
run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository
run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository -Prelease

View File

@ -44,6 +44,7 @@ jobs:
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

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ build
.vscode
lib/bin
wrapper/javacord/bin
wrapper/discord4j/bin

View File

@ -8,6 +8,11 @@ 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())
}
}
}
subprojects {
@ -18,6 +23,8 @@ subprojects {
publishing {
publications {
create<MavenPublication>("maven") {
if (!project.hasProperty("release"))
artifactId = project.getName() + "-dev"
from(components["java"])
}
}

View File

@ -3,12 +3,16 @@
[versions]
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"
[libraries]
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"}
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"}

View File

@ -20,14 +20,14 @@ dependencies {
testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation(libs.log4j)
implementation(libs.slf4j)
implementation(libs.geantyref)
}
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
languageVersion = JavaLanguageVersion.of(23)
}
}

View File

@ -1,22 +1,65 @@
package net.tomatentum.marinara;
import org.slf4j.Logger;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.processor.AutocompleteInteractionProcessor;
import net.tomatentum.marinara.interaction.processor.DirectInteractionProcessor;
import net.tomatentum.marinara.reflection.MethodExecutor;
import net.tomatentum.marinara.reflection.ProcessorMethodExecutor;
import net.tomatentum.marinara.reflection.ReflectedMethodFactory;
import net.tomatentum.marinara.reflection.ReflectedMethodFactoryImpl;
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
import net.tomatentum.marinara.registry.InteractionRegistry;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.wrapper.IdentifierProvider;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class Marinara {
private Logger logger = LoggerUtil.getLogger(getClass());
public static <T extends LibraryWrapper> Marinara load(LibraryWrapper wrapper) {
InteractionRegistry registry = new InteractionRegistry(wrapper);
return new Marinara(registry);
public static Marinara load(LibraryWrapper wrapper) {
return new Marinara(wrapper);
}
private LibraryWrapper wrapper;
private ReflectedMethodFactory reflectedMethodFactory;
private InteractionRegistry registry;
private InteractionCheckRegistry checkRegistry;
private MethodExecutor interactionExecutor;
private Marinara(InteractionRegistry registry) {
this.registry = registry;
private Marinara(LibraryWrapper wrapper) {
this.wrapper = wrapper;
this.reflectedMethodFactory = new ReflectedMethodFactoryImpl(this);
this.registry = new InteractionRegistry(this);
this.checkRegistry = new InteractionCheckRegistry();
IdentifierProvider provider = wrapper.createIdentifierProvider();
this.interactionExecutor = (MethodExecutor) new ProcessorMethodExecutor()
.addProcessor(new DirectInteractionProcessor(getRegistry(), provider, InteractionType.COMMAND, InteractionType.BUTTON))
.addProcessor(new AutocompleteInteractionProcessor(this, provider));
wrapper.subscribeInteractions(this.interactionExecutor::handle);
logger.info("Marinara loaded successfully!");
}
public LibraryWrapper getWrapper() {
return this.wrapper;
}
public InteractionRegistry getRegistry() {
return registry;
return this.registry;
}
public InteractionCheckRegistry getCheckRegistry() {
return this.checkRegistry;
}
public ReflectedMethodFactory getReflectedMethodFactory() {
return this.reflectedMethodFactory;
}
public MethodExecutor getInteractionExecutor() {
return interactionExecutor;
}
}

View File

@ -0,0 +1,50 @@
package net.tomatentum.marinara.checks;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.slf4j.Logger;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public record AppliedCheck(InteractionCheck<?> check, Annotation annotation) {
private static Logger logger = LoggerUtil.getLogger(AppliedCheck.class);
public boolean pre(Object context) {
Method[] methods = Arrays.stream(check.getClass().getMethods())
.filter(x -> x.getName().equals("preExec"))
.filter(x -> !x.isBridge())
.toArray(s -> new Method[s]);
Method method = ReflectionUtil.getMostSpecificMethod(methods, context.getClass(), annotation.annotationType());
method.setAccessible(true);
try {
logger.debug("Executing pre check {} with context {}", check.getClass().getName(), context.toString());
boolean result = (boolean) method.invoke(check, context, annotation);
logger.debug("Pre Check {} {} with context {}", check.getClass().getName(), result ? "succeeded" : "failed", context.toString());
return result;
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
logger.error("Failed executing pre-check", e);
return false;
}
}
public void post(Object context) {
Method[] methods = Arrays.stream(check.getClass().getMethods())
.filter(x -> x.getName().equals("postExec"))
.filter(x -> !x.isBridge())
.toArray(s -> new Method[s]);
Method method = ReflectionUtil.getMostSpecificMethod(methods, context.getClass(), annotation.annotationType());
method.setAccessible(true);
try {
logger.debug("Executing post check {} with context {}", check.getClass().getName(), context.toString());
method.invoke(check, context, annotation);
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
logger.error("Failed executing post-check", e);
}
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -1,70 +0,0 @@
package net.tomatentum.marinara.interaction.commands;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
public record ExecutableSlashCommandDefinition(
SlashCommand applicationCommand,
SubCommand subCommand,
SubCommandGroup subCommandGroup,
SlashCommandOption[] options) {
@Override
public final boolean equals(Object o) {
if (!(o instanceof ExecutableSlashCommandDefinition))
return false;
ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o;
return other.applicationCommand.name().equals(this.applicationCommand.name()) &&
other.subCommandGroup.name().equals(this.subCommandGroup.name()) &&
other.subCommand.name().equals(this.subCommand.name());
}
@Override
public final String toString() {
return applicationCommand.name() + subCommand.name() != null ? "::" + subCommand.name() : "";
}
public boolean isRootCommand() {
return subCommand == null;
}
public static class Builder {
private SlashCommand applicationCommand;
private SubCommand subCommand;
private SubCommandGroup subCommandGroup;
public ExecutableSlashCommandDefinition build() {
if (applicationCommand == null)
throw new IllegalArgumentException("applicationCommandName cant be null");
return new ExecutableSlashCommandDefinition(applicationCommand, subCommand, subCommandGroup, subCommand != null ? subCommand.options() : applicationCommand.options());
}
public void setApplicationCommand(SlashCommand applicationCommand) {
this.applicationCommand = applicationCommand;
}
public void setSubCommand(SubCommand subCommand) {
this.subCommand = subCommand;
}
public void setSubCommandGroup(SubCommandGroup subCommandGroup) {
this.subCommandGroup = subCommandGroup;
}
public SlashCommand getApplicationCommand() {
return applicationCommand;
}
public SubCommand getSubCommand() {
return subCommand;
}
public SubCommandGroup getSubCommandGroup() {
return subCommandGroup;
}
}
}

View File

@ -1,99 +1,112 @@
package net.tomatentum.marinara.interaction.commands;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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 org.slf4j.Logger;
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;
import net.tomatentum.marinara.util.LoggerUtil;
public class SlashCommandDefinition {
private List<ExecutableSlashCommandDefinition> executableDefinitons;
private SlashCommand slashCommand;
private boolean isRootCommand;
public SlashCommandDefinition(SlashCommand applicationCommand) {
this.executableDefinitons = new ArrayList<>();
this.slashCommand = applicationCommand;
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;
}
public SlashCommandDefinition addExecutableCommand(ExecutableSlashCommandDefinition def) {
if (def.applicationCommand() != null) {
if (slashCommand == null)
this.slashCommand = def.applicationCommand();
if (!this.slashCommand.name().equals(def.applicationCommand().name()))
throw new IllegalArgumentException(def + ": has a non matching Application Command description. Please edit it to equal all other descriptions or remove it to use other definitions descriptions");
}
private Set<InteractionIdentifier> entries;
private RootCommandIdentifier rootIdentifier;
private boolean isRootCommand;
if (executableDefinitons.isEmpty())
this.isRootCommand = def.isRootCommand();
private Logger logger = LoggerUtil.getLogger(getClass());
if ((isRootCommand && !def.isRootCommand()) || (!isRootCommand && def.isRootCommand())) {
throw new IllegalArgumentException(def + ": cannot have subcommands and rootcommand definitions together");
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");
}
executableDefinitons.add(def);
entries.add(identifier);
this.logger.debug("Added identifer {} to command {}", identifier, rootIdentifier);
return this;
}
public SubCommandGroup[] getSubCommandGroups() {
List<SubCommandGroup> subCommandGroups = Arrays.stream(getExecutableDefinitons())
.filter((x) -> x.subCommandGroup() != null)
.map((x) -> x.subCommandGroup())
.toList();
HashMap<String, SubCommandGroup> subCommandGroupMap = new HashMap<>();
subCommandGroups.forEach((x) -> {
SubCommandGroup current = subCommandGroupMap.get(x.name());
if (current == null || (current.description().isBlank() && !x.description().isBlank()))
subCommandGroupMap.put(x.name(), x);
});
return subCommandGroupMap.values().toArray(new SubCommandGroup[0]);
}
public SubCommand[] getSubCommands(String groupName) {
List<SubCommand> subCommands;
if (groupName == null)
subCommands = Arrays.stream(getExecutableDefinitons())
.filter((x) -> x.subCommandGroup() == null && x.subCommand() != null)
.map((x) -> x.subCommand())
.toList();
else
subCommands = Arrays.stream(getExecutableDefinitons())
.filter((x) -> x.subCommandGroup().name().equals(groupName) && x.subCommand() != null)
.map((x) -> x.subCommand())
.toList();
public SlashCommandIdentifier[] getSubCommandGroups() {
if (isRootCommand)
return null;
HashMap<String, SubCommand> subCommandMap = new HashMap<>();
subCommands.forEach((x) -> {
SubCommand current = subCommandMap.get(x.name());
if (current == null || (current.description().isBlank() && !x.description().isBlank()))
subCommandMap.put(x.name(), x);
});
List<InteractionIdentifier> subCommandGroups = entries().stream()
.filter(x -> x.parent().parent() != null)
.map(x -> x.parent())
.toList();
return subCommandMap.values().toArray(new SubCommand[0]);
return subCommandGroups.toArray(SlashCommandIdentifier[]::new);
}
public SlashCommand getFullSlashCommand() {
if (isRootCommand())
return getSlashCommand();
for (ExecutableSlashCommandDefinition executableSlashCommandDefinition : executableDefinitons) {
if (executableSlashCommandDefinition.options().length > 0)
return executableSlashCommandDefinition.applicationCommand();
}
return null;
public SlashCommandIdentifier[] getSubCommands() {
if (isRootCommand)
return null;
return entries.stream().filter(x -> x.parent() instanceof RootCommandIdentifier).toArray(SlashCommandIdentifier[]::new);
}
public SlashCommand getSlashCommand() {
return slashCommand;
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);
}
public ExecutableSlashCommandDefinition[] getExecutableDefinitons() {
return executableDefinitons.toArray(new ExecutableSlashCommandDefinition[0]);
@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() {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -5,6 +5,7 @@ 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})
@ -14,4 +15,11 @@ public @interface SlashCommandOption {
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 {
}
}

View File

@ -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 "";
}

View File

@ -0,0 +1,5 @@
package net.tomatentum.marinara.interaction.commands.choice;
public interface ChoiceValueProvider<T> {
T getChoiceValue();
}

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -1,16 +1,35 @@
package net.tomatentum.marinara.interaction.commands.option;
public enum SlashCommandOptionType {
ATTACHMENT,
BOOLEAN,
CHANNEL,
DECIMAL,
LONG,
MENTIONABLE,
ROLE,
STRING,
SUB_COMMAND,
SUB_COMMAND_GROUP,
UNKNOW,
USER
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;
}
}

View File

@ -0,0 +1,64 @@
package net.tomatentum.marinara.interaction.components.methods;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.interaction.methods.InteractionMethod;
import net.tomatentum.marinara.parser.AnnotationParser;
import net.tomatentum.marinara.parser.ButtonParser;
import net.tomatentum.marinara.reflection.ReflectedMethod;
public class ButtonInteractionMethod extends InteractionMethod {
private String customId;
private ButtonInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) {
super(method, handler, marinara);
}
@Override
public Object getParameter(Object context, int index) {
Class<?> type = method().getParameterTypes()[index+1];
return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type);
}
@Override
public InteractionIdentifier identifier() {
return InteractionIdentifier.builder()
.name(customId)
.description("Button")
.type(InteractionType.BUTTON)
.build();
}
public static class Factory extends InteractionMethod.Factory {
@Override
public Optional<ReflectedMethod> produce(Marinara marinara, Method method, Object containingObject) {
ReflectedMethod rMethod = null;
if (method.isAnnotationPresent(Button.class) &&
(containingObject instanceof InteractionHandler iHandler)
)
rMethod = new ButtonInteractionMethod(method, iHandler, marinara);
return Optional.ofNullable(rMethod);
}
@Override
public void addParser(ReflectedMethod method, List<AnnotationParser> parser) {
super.addParser(method, parser);
parser.add(
new ButtonParser(method.method(), x -> ((ButtonInteractionMethod) method).customId = x)
);
}
}
}

View File

@ -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();
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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,77 @@
package net.tomatentum.marinara.interaction.methods;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.annotation.AutoComplete;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.parser.AnnotationParser;
import net.tomatentum.marinara.parser.AutocompleteParser;
import net.tomatentum.marinara.reflection.ReflectedMethod;
public class AutoCompleteInteractionMethod extends InteractionMethod {
private String autocompleteRef;
private AutoCompleteInteractionMethod(Method method,
InteractionHandler handler,
Marinara marinara
) {
super(method, handler, marinara);
}
@Override
public Object getParameter(Object context, int index) {
Class<?> type = method().getParameterTypes()[index+1];
Object contextObject = marinara.getWrapper().getContextObjectProvider().getInteractionContextObject(context, type);
if (contextObject != null)
return contextObject;
Object autocompleteOptionValue = marinara.getWrapper().getContextObjectProvider().getAutocompleteFocusedOption(context).input();
if (type.isInstance(autocompleteOptionValue))
return autocompleteOptionValue;
return null;
}
@Override
public InteractionIdentifier identifier() {
return InteractionIdentifier.builder()
.type(InteractionType.AUTOCOMPLETE)
.name(autocompleteRef)
.description("AUTOCOMPLETE")
.build();
}
public static class Factory extends InteractionMethod.Factory {
@Override
public Optional<ReflectedMethod> produce(Marinara marinara, Method method, Object containingObject) {
ReflectedMethod rMethod = null;
if ((containingObject instanceof InteractionHandler iHandler) &&
method.isAnnotationPresent(AutoComplete.class) &&
!(method.isAnnotationPresent(SlashCommand.class) ||
method.isAnnotationPresent(SubCommand.class)))
rMethod = new AutoCompleteInteractionMethod(method, iHandler, marinara);
return Optional.ofNullable(rMethod);
}
@Override
public void addParser(ReflectedMethod method, List<AnnotationParser> parser) {
super.addParser(method, parser);
parser.add(
new AutocompleteParser(method.method(), x -> ((AutoCompleteInteractionMethod) method).autocompleteRef = x[0])
);
}
}
}

View File

@ -1,40 +0,0 @@
package net.tomatentum.marinara.interaction.methods;
import java.lang.reflect.Method;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class ButtonInteractionMethod extends InteractionMethod {
private String customId;
ButtonInteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
super(method, handler, wrapper);
parseMethod();
}
@Override
public Object getParameter(Object parameter, int index) {
Class<?> type = getMethod().getParameterTypes()[index+1];
return wrapper.getComponentContextObject(parameter, type);
}
@Override
public boolean canRun(Object context) {
return wrapper.getButtonId(context).equals(customId);
}
@Override
public InteractionType getType() {
return InteractionType.BUTTON;
}
private void parseMethod() {
Button button = getMethod().getAnnotation(Button.class);
this.customId = button.value();
}
}

View File

@ -1,68 +1,66 @@
package net.tomatentum.marinara.interaction.methods;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.checks.AppliedCheck;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.parser.AnnotationParser;
import net.tomatentum.marinara.parser.InteractionCheckParser;
import net.tomatentum.marinara.reflection.ReflectedMethod;
import net.tomatentum.marinara.reflection.ReflectedMethodFactory;
public abstract class InteractionMethod {
public abstract class InteractionMethod extends ReflectedMethod {
public static InteractionMethod create(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
if (method.isAnnotationPresent(SlashCommand.class) || method.isAnnotationPresent(SubCommand.class))
return new SlashCommandInteractionMethod(method, handler, wrapper);
if (method.isAnnotationPresent(Button.class))
return new ButtonInteractionMethod(method, handler, wrapper);
return null;
protected Marinara marinara;
protected List<AppliedCheck> appliedChecks;
protected InteractionMethod(
Method method,
InteractionHandler handler,
Marinara marinara
) {
super(method, handler);
this.marinara = marinara;
this.appliedChecks = new ArrayList<>();
}
@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;
}
protected Method method;
protected InteractionHandler handler;
protected LibraryWrapper wrapper;
public abstract InteractionIdentifier identifier();
protected InteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
if (!Arrays.asList(handler.getClass().getMethods()).contains(method))
throw new InvalidParameterException("Method does not apply to specified handler");
this.method = method;
this.handler = handler;
this.wrapper = wrapper;
public Marinara marinara() {
return this.marinara;
}
public abstract Object getParameter(Object parameter, int index);
public List<AppliedCheck> appliedChecks() {
return this.appliedChecks;
}
public abstract boolean canRun(Object context);
public abstract static class Factory implements ReflectedMethodFactory.Factory {
public abstract InteractionType getType();
public void run(Object context) {
int parameterCount = method.getParameterCount();
List<Object> parameters = new ArrayList<>();
for (int i = 0; i < parameterCount; i++) {
if (i == 0) {
parameters.add(context);
continue;
}
parameters.add(getParameter(context, i-1));
@Override
public void addParser(ReflectedMethod method, List<AnnotationParser> parser) {
InteractionMethod imethod = (InteractionMethod) method;
parser.add(
new InteractionCheckParser(method.method(), imethod.appliedChecks::add, imethod.marinara().getCheckRegistry())
);
}
method.setAccessible(true);
try {
method.invoke(handler, parameters.toArray());
}catch (IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
public Method getMethod() {
return method;
}
}

View File

@ -1,63 +1,58 @@
package net.tomatentum.marinara.interaction.methods;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
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.util.ReflectionUtil;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier;
import net.tomatentum.marinara.parser.AnnotationParser;
import net.tomatentum.marinara.parser.SlashCommandParser;
import net.tomatentum.marinara.reflection.ReflectedMethod;
public class SlashCommandInteractionMethod extends InteractionMethod {
private ExecutableSlashCommandDefinition commandDefinition;
private SlashCommandIdentifier interactionIdentifier;
SlashCommandInteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
super(method, handler, wrapper);
parseMethod();
private SlashCommandInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) {
super(method, handler, marinara);
}
@Override
public Object getParameter(Object context, int index) {
return wrapper.convertCommandOption(context, commandDefinition.options()[index].type(), commandDefinition.options()[index].name());
return marinara.getWrapper().getContextObjectProvider().convertCommandOption(context, interactionIdentifier.options()[index].name());
}
@Override
public boolean canRun(Object context) {
ExecutableSlashCommandDefinition other = wrapper.getCommandDefinition(context);
return commandDefinition.equals(other);
public InteractionIdentifier identifier() {
return interactionIdentifier;
}
@Override
public InteractionType getType() {
return InteractionType.COMMAND;
}
public static class Factory extends InteractionMethod.Factory {
public ExecutableSlashCommandDefinition getCommandDefinition() {
return commandDefinition;
}
private void parseMethod() {
ReflectionUtil.checkValidCommandMethod(method);
SlashCommand cmd = ReflectionUtil.getAnnotation(method, SlashCommand.class);
ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder();
builder.setApplicationCommand(cmd);
if (ReflectionUtil.isAnnotationPresent(method, SubCommandGroup.class)) {
SubCommandGroup cmdGroup = ReflectionUtil.getAnnotation(method, SubCommandGroup.class);
builder.setSubCommandGroup(cmdGroup);
@Override
public Optional<ReflectedMethod> produce(Marinara marinara, Method method, Object containingObject) {
ReflectedMethod rMethod = null;
if ((containingObject instanceof InteractionHandler iHandler) &&
(method.isAnnotationPresent(SlashCommand.class) ||
method.isAnnotationPresent(SubCommand.class)))
rMethod = new SlashCommandInteractionMethod(method, iHandler, marinara);
return Optional.ofNullable(rMethod);
}
if (ReflectionUtil.isAnnotationPresent(method, SubCommand.class)) {
SubCommand subCmd = ReflectionUtil.getAnnotation(method, SubCommand.class);
builder.setSubCommand(subCmd);
@Override
public void addParser(ReflectedMethod method, List<AnnotationParser> parser) {
super.addParser(method, parser);
parser.add(
new SlashCommandParser(method.method(), x -> ((SlashCommandInteractionMethod) method).interactionIdentifier = x)
);
}
this.commandDefinition = builder.build();
}
}

View File

@ -0,0 +1,53 @@
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.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier;
import net.tomatentum.marinara.registry.InteractionEntry;
import net.tomatentum.marinara.wrapper.IdentifierProvider;
public class AutocompleteInteractionProcessor extends InteractionMethodProcessor {
private Marinara marinara;
public AutocompleteInteractionProcessor(Marinara marinara, IdentifierProvider provider) {
super(provider, Set.of(InteractionType.AUTOCOMPLETE));
this.marinara = marinara;
}
@Override
public void processInteraction(Object context, InteractionIdentifier identifier) {
Optional<InteractionEntry> entry = this.marinara.getRegistry().findFor(convertToCommandIdentifier(identifier));
if (entry.isPresent() && entry.get().identifier() instanceof SlashCommandIdentifier sIdent) {
List<String> autocompleteRefs = Arrays.asList(this.marinara.getWrapper().getContextObjectProvider()
.getAutocompleteFocusedOption(context).getAutocompleteRefs(sIdent.options()));
List<Object> results = this.marinara.getRegistry().interactions().stream()
.filter(e -> e.type().equals(InteractionType.AUTOCOMPLETE))
.filter(e -> autocompleteRefs.contains(e.identifier().name()))
.map(e -> e.runAll(context))
.flatMap(Arrays::stream)
.filter(Objects::nonNull)
.toList();
if (!results.isEmpty())
marinara.getWrapper().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();
}
}

View File

@ -0,0 +1,27 @@
package net.tomatentum.marinara.interaction.processor;
import java.util.Set;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.registry.InteractionRegistry;
import net.tomatentum.marinara.wrapper.IdentifierProvider;
public class DirectInteractionProcessor extends InteractionMethodProcessor {
private InteractionRegistry registry;
public DirectInteractionProcessor(InteractionRegistry registry, IdentifierProvider provider, InteractionType... types) {
super(provider, Set.of(types));
this.registry = registry;
}
@Override
protected void processInteraction(Object context, InteractionIdentifier identifier) {
this.registry.interactions().stream()
.filter(e -> e.identifier().equals(identifier))
.findFirst()
.ifPresent(e -> e.runAll(context));
}
}

View File

@ -0,0 +1,35 @@
package net.tomatentum.marinara.interaction.processor;
import java.util.Set;
import org.slf4j.Logger;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.reflection.MethodProcessor;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.wrapper.IdentifierProvider;
public abstract class InteractionMethodProcessor implements MethodProcessor {
private Logger logger = LoggerUtil.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) {
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, identifier);
}
protected abstract void processInteraction(Object context, InteractionIdentifier identifier);
}

View File

@ -0,0 +1,8 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
public interface AnnotationParser {
void parse();
Method getMethod();
}

View File

@ -0,0 +1,33 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Consumer;
import net.tomatentum.marinara.interaction.annotation.AutoComplete;
public class AutocompleteParser implements AnnotationParser {
private Method method;
private Consumer<String[]> consumer;
public AutocompleteParser(Method method, Consumer<String[]> consumer) {
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
String[] autocompletes = Arrays.stream(this.method.getAnnotationsByType(AutoComplete.class))
.map(AutoComplete::value)
.toArray(String[]::new);
this.consumer.accept(autocompletes);
}
@Override
public Method getMethod() {
return this.method;
}
}

View File

@ -0,0 +1,36 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import org.slf4j.Logger;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public class ButtonParser implements AnnotationParser {
private Method method;
private Consumer<String> consumer;
private Logger logger = LoggerUtil.getLogger(getClass());
public ButtonParser(Method method, Consumer<String> consumer) {
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
Button button = getMethod().getAnnotation(Button.class);
logger.trace("Parsed Button annotation {} for method {}", button.toString(), ReflectionUtil.getFullMethodName(method));
this.consumer.accept(button.value());
}
@Override
public Method getMethod() {
return this.method;
}
}

View File

@ -0,0 +1,51 @@
package net.tomatentum.marinara.parser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Consumer;
import org.slf4j.Logger;
import net.tomatentum.marinara.checks.AppliedCheck;
import net.tomatentum.marinara.checks.InteractionCheck;
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public class InteractionCheckParser implements AnnotationParser {
private InteractionCheckRegistry checkRegistry;
private Method method;
private Consumer<AppliedCheck> consumer;
private Logger logger = LoggerUtil.getLogger(getClass());
public InteractionCheckParser(Method method, Consumer<AppliedCheck> consumer, InteractionCheckRegistry checkRegistry) {
this.checkRegistry = checkRegistry;
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
Annotation[] annotations = method.getAnnotations();
Arrays.stream(annotations).forEach(this::convertAnnotation);
}
private void convertAnnotation(Annotation annotation) {
Optional<InteractionCheck<?>> check = this.checkRegistry.getCheckFromAnnotation(annotation.annotationType());
if (check.isPresent()) {
AppliedCheck appliedCheck = new AppliedCheck(check.get(), annotation);
logger.trace("Parsed InteractionCheck {} for annotation {} for method {}", check.getClass().getName(), annotation.toString(), ReflectionUtil.getFullMethodName(method));
consumer.accept(appliedCheck);
}
}
@Override
public Method getMethod() {
return this.method;
}
}

View File

@ -0,0 +1,84 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import org.slf4j.Logger;
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;
import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public class SlashCommandParser implements AnnotationParser {
private Method method;
private Consumer<SlashCommandIdentifier> consumer;
private Logger logger = LoggerUtil.getLogger(getClass());
public SlashCommandParser(Method method, Consumer<SlashCommandIdentifier> consumer) {
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
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.toString());
consumer.accept((SlashCommandIdentifier) lastIdentifier);
}
@Override
public Method getMethod() {
return this.method;
}
private void checkValidCommandMethod(Method method) {
if (method.isAnnotationPresent(SlashCommand.class) &&
method.getDeclaringClass().isAnnotationPresent(SlashCommand.class)) {
throw new RuntimeException(method.getName() + ": Can't have ApplicationCommand Annotation on Class and Method");
}
if (!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class))
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Class or Method");
if ((method.isAnnotationPresent(SubCommand.class) &&
!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class))) {
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Method or Class");
}
}
}

View File

@ -0,0 +1,7 @@
package net.tomatentum.marinara.reflection;
public interface MethodExecutor {
void handle(Object context);
}

View File

@ -0,0 +1,7 @@
package net.tomatentum.marinara.reflection;
public interface MethodProcessor {
void process(Object context);
}

View File

@ -0,0 +1,7 @@
package net.tomatentum.marinara.reflection;
public interface ProcessorContainer {
ProcessorContainer addProcessor(MethodProcessor processor);
}

View File

@ -0,0 +1,32 @@
package net.tomatentum.marinara.reflection;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import net.tomatentum.marinara.util.LoggerUtil;
public class ProcessorMethodExecutor implements MethodExecutor, ProcessorContainer {
private Logger logger = LoggerUtil.getLogger(getClass());
private Set<MethodProcessor> processors;
public ProcessorMethodExecutor() {
this.processors = new HashSet<>();
}
@Override
public ProcessorContainer addProcessor(MethodProcessor processor) {
processors.add(processor);
return this;
}
@Override
public void handle(Object context) {
logger.debug("Received {} interaction ", context);
processors.forEach(x -> x.process(context));
}
}

View File

@ -0,0 +1,66 @@
package net.tomatentum.marinara.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public abstract class ReflectedMethod {
private Logger logger = LoggerUtil.getLogger(getClass());
private Method method;
private Object containingObject;
public ReflectedMethod(Method method, Object containingObject) {
if (!Arrays.asList(containingObject.getClass().getMethods()).contains(method))
throw new InvalidParameterException("Method does not apply to specified handler");
this.method = method;
this.containingObject = containingObject;
}
public abstract Object getParameter(Object context, int index);
public Object run(Object context) {
method.setAccessible(true);
try {
return method.invoke(containingObject, getParameters(context));
}catch (IllegalAccessException | InvocationTargetException ex) {
logger.error("ReflectedMethod failed to run", ex);
return null;
}
}
public Method method() {
return this.method;
}
public Object containingObject() {
return this.containingObject;
}
private Object[] getParameters(Object context) {
int parameterCount = method.getParameterCount();
List<Object> parameters = new ArrayList<>();
for (int i = 0; i < parameterCount; i++) {
Object parameter;
if (i == 0) {
parameter = context;
}else
parameter = getParameter(context, i-1);
logger.trace("Found parameter {}={} for method {}", parameter != null ? parameter.getClass().toString() : " ", parameter, ReflectionUtil.getFullMethodName(method));
parameters.add(parameter);
}
return parameters.toArray();
}
}

View File

@ -0,0 +1,20 @@
package net.tomatentum.marinara.reflection;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.parser.AnnotationParser;
public interface ReflectedMethodFactory {
Optional<ReflectedMethod> produce(Method method, Object containingClass);
ReflectedMethodFactory addFactory(Factory factory);
public interface Factory {
Optional<ReflectedMethod> produce(Marinara marinara, Method method, Object containingObject);
void addParser(ReflectedMethod method, List<AnnotationParser> parser);
}
}

View File

@ -0,0 +1,62 @@
package net.tomatentum.marinara.reflection;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.parser.AnnotationParser;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public class ReflectedMethodFactoryImpl implements ReflectedMethodFactory {
private Logger logger = LoggerUtil.getLogger(getClass());
private Marinara marinara;
private List<Factory> factories;
public ReflectedMethodFactoryImpl(Marinara marinara) {
this(marinara, new ArrayList<>());
}
public ReflectedMethodFactoryImpl(Marinara marinara, List<Factory> factories) {
this.marinara = marinara;
this.factories = factories;
}
@Override
public Optional<ReflectedMethod> produce(Method method, Object containingClass) {
Optional<ReflectedMethod> imethod = this.factories.stream()
.map(f -> factoryProduce(f, method, containingClass))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
if (imethod.isEmpty()) {
logger.debug("Could not produce a ReflectedMethod for Method {}", ReflectionUtil.getFullMethodName(method));
}
return imethod;
}
@Override
public ReflectedMethodFactory addFactory(Factory factory) {
this.factories.add(factory);
return this;
}
private Optional<ReflectedMethod> factoryProduce(Factory factory, Method method, Object containingClass) {
List<AnnotationParser> parser = new ArrayList<>();
Optional<ReflectedMethod> m = factory.produce(this.marinara, method, containingClass);
m.ifPresent(x -> {
factory.addParser(x, parser);
parser.forEach(AnnotationParser::parse);
});
return m;
}
}

View File

@ -0,0 +1,40 @@
package net.tomatentum.marinara.registry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import io.leangen.geantyref.GenericTypeReflector;
import net.tomatentum.marinara.checks.InteractionCheck;
import net.tomatentum.marinara.util.LoggerUtil;
public class InteractionCheckRegistry {
private List<InteractionCheck<?>> checks;
private Logger logger = LoggerUtil.getLogger(getClass());
public InteractionCheckRegistry() {
this.checks = new ArrayList<>();
}
public void addCheck(InteractionCheck<?> check) {
checks.add(check);
logger.info("Registered Check {}", check.getClass().getName());
}
public Optional<InteractionCheck<?>> getCheckFromAnnotation(Type annotation) {
for (InteractionCheck<?> interactionCheck : checks) {
ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(interactionCheck.getClass(), InteractionCheck.class);
Type typeParam = type.getActualTypeArguments()[0];
if (typeParam.equals(annotation))
return Optional.of(interactionCheck);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,90 @@
package net.tomatentum.marinara.registry;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.slf4j.Logger;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.interaction.methods.InteractionMethod;
import net.tomatentum.marinara.util.LoggerUtil;
public class InteractionEntry {
public static InteractionEntry findEntry(Set<InteractionEntry> entries, InteractionIdentifier identifier) {
Optional<InteractionEntry> oentry = entries.stream()
.filter(i -> i.identifier().equals(identifier))
.findFirst();
InteractionEntry entry = oentry.orElse(new InteractionEntry(identifier));
if (oentry.isEmpty()) entries.add(entry);
return entry;
}
private InteractionIdentifier identifier;
private Set<InteractionMethod> methods;
private Logger logger = LoggerUtil.getLogger(getClass());
public InteractionEntry(InteractionIdentifier identifier) {
this.identifier = identifier;
this.methods = new HashSet<>();
}
public InteractionEntry addMethod(InteractionMethod method) {
InteractionIdentifier midentifier = method.identifier();
if (!this.identifier().equals(midentifier))
throw new IllegalArgumentException("Method's identifier did not equal the entry's identifier");
this.methods.add(method);
InteractionIdentifier.tryAddDescriptions(midentifier, midentifier);
logger.debug("Added method {} to entry {}", method.method().getName(), this.identifier);
return this;
}
public Object[] runAll(Object context) {
return this.methods.stream()
.map(x -> {
logger.debug("Running Method {} from {} with context {}", x, this, context);
return x.run(context);
})
.flatMap(o -> o instanceof Object[] oArray ? Arrays.stream(oArray) : Stream.<Object>of(o))
.toArray();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof InteractionEntry))
return false;
InteractionEntry other = (InteractionEntry) obj;
return other.identifier().equals(identifier());
}
@Override
public int hashCode() {
return this.identifier().hashCode();
}
@Override
public String toString() {
return "InteractionEntry(%s)".formatted(identifier().toString());
}
public InteractionType type() {
return identifier().type();
}
public InteractionIdentifier identifier() {
return identifier;
}
public Set<InteractionMethod> methods() {
return methods;
}
}

View File

@ -1,61 +1,80 @@
package net.tomatentum.marinara.registry;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.slf4j.Logger;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod;
import net.tomatentum.marinara.interaction.components.methods.ButtonInteractionMethod;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ObjectAggregator;
import net.tomatentum.marinara.interaction.methods.AutoCompleteInteractionMethod;
import net.tomatentum.marinara.interaction.methods.InteractionMethod;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod;
import net.tomatentum.marinara.reflection.ReflectedMethod;
public class InteractionRegistry {
private List<InteractionMethod> interactionMethods;
private LibraryWrapper wrapper;
private Logger logger = LoggerUtil.getLogger(getClass());
private Set<InteractionEntry> interactions;
private Marinara marinara;
public InteractionRegistry(Marinara marinara) {
this.interactions = new HashSet<>();
this.marinara = marinara;
marinara.getReflectedMethodFactory()
.addFactory(new AutoCompleteInteractionMethod.Factory())
.addFactory(new SlashCommandInteractionMethod.Factory())
.addFactory(new ButtonInteractionMethod.Factory());
public InteractionRegistry(LibraryWrapper wrapper) {
this.interactionMethods = new ArrayList<>();
this.wrapper = wrapper;
wrapper.subscribeInteractions(this::handle);
}
public void addInteractions(InteractionHandler interactionHandler) {
for (Method method : interactionHandler.getClass().getMethods()) {
InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, wrapper);
if (iMethod != null)
this.interactionMethods.add(iMethod);
for (Method method : interactionHandler.getClass().getDeclaredMethods()) {
Optional<ReflectedMethod> rMethod = this.marinara.getReflectedMethodFactory().produce(method, interactionHandler);
rMethod.ifPresent(x -> {
if (x instanceof InteractionMethod) {
InteractionMethod iMethod = (InteractionMethod) x;
InteractionEntry.findEntry(interactions, iMethod.identifier()).addMethod(iMethod);
logger.debug("Added {} method from {}", iMethod.method().getName(), interactionHandler.getClass().getSimpleName());
}
});
}
logger.info("Added all Interactions from {}", interactionHandler.getClass().getSimpleName());
}
public void registerCommands() {
List<SlashCommandDefinition> defs = new ArrayList<>();
List<ExecutableSlashCommandDefinition> execDefs = interactionMethods.stream()
.filter((x) -> x.getClass().isAssignableFrom(SlashCommandInteractionMethod.class))
.map((x) -> ((SlashCommandInteractionMethod)x).getCommandDefinition())
List<InteractionIdentifier> slashIdentifiers = interactions.stream()
.filter((x) -> x.type().equals(InteractionType.COMMAND))
.map((x) -> x.identifier())
.toList();
execDefs.forEach((def) -> {
Optional<SlashCommandDefinition> appDef = defs.stream()
.filter((x) -> x.getSlashCommand().equals(def.applicationCommand()))
.findFirst();
if (appDef.isPresent())
appDef.get().addExecutableCommand(def);
else
defs.add(new SlashCommandDefinition(def.applicationCommand()).addExecutableCommand(def));
});
SlashCommandDefinition[] defs = new ObjectAggregator<InteractionIdentifier, RootCommandIdentifier, SlashCommandDefinition>(
i -> Arrays.asList((RootCommandIdentifier)i.rootNode()),
SlashCommandDefinition::addIdentifier,
SlashCommandDefinition::new)
.aggregate(slashIdentifiers)
.toArray(SlashCommandDefinition[]::new);
wrapper.registerSlashCommands(defs.toArray(new SlashCommandDefinition[0]));
marinara.getWrapper().getRegisterer().register(defs);
}
public void handle(Object context) {
interactionMethods.forEach((m) -> {
InteractionType type = wrapper.getInteractionType(context.getClass());
if (m.getType().equals(type))
m.run(context);
});
public Optional<InteractionEntry> findFor(InteractionIdentifier identifier) {
return this.interactions.stream()
.filter(x -> x.identifier().equals(identifier))
.findFirst();
}
public Set<InteractionEntry> interactions() {
return this.interactions;
}
}

View File

@ -0,0 +1,17 @@
package net.tomatentum.marinara.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.NOPLoggerFactory;
public class LoggerUtil {
public static Logger getLogger(String name) {
if (LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory)
return new SimpleLogger(name);
return LoggerFactory.getLogger(name);
}
public static Logger getLogger(Class<?> clazz) {
return getLogger(clazz.getName());
}
}

View File

@ -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();
}
}

View File

@ -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<>());
}
}

View File

@ -2,9 +2,10 @@ package net.tomatentum.marinara.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public final class ReflectionUtil {
@ -21,19 +22,86 @@ public final class ReflectionUtil {
method.getDeclaringClass().getAnnotation(annotationClass);
}
public static void checkValidCommandMethod(Method method) {
if (method.isAnnotationPresent(SlashCommand.class) &&
method.getDeclaringClass().isAnnotationPresent(SlashCommand.class)) {
throw new RuntimeException(method.getName() + ": Can't have ApplicationCommand Annotation on Class and Method");
public static int getCastDepth(Class<?> child, Class<?> parent) {
if (parent.equals(Object.class))
return Integer.MAX_VALUE;
if (!parent.isAssignableFrom(child)) {
throw new IllegalArgumentException("The specified class is not a child class of the specified parent.");
}
if (!isAnnotationPresent(method, SlashCommand.class))
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Class or Method");
int depth = 0;
Class<?> curr = child;
List<Class<?>> parents = new ArrayList<>();
if ((method.isAnnotationPresent(SubCommand.class) &&
!isAnnotationPresent(method, SlashCommand.class))) {
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Method or Class");
while (!curr.equals(parent)) {
depth++;
parents.add(curr.getSuperclass());
parents.addAll(Arrays.asList(curr.getInterfaces()));
for (Class<?> currParent : parents) {
if (currParent != null && parent.isAssignableFrom(currParent)) {
curr = currParent;
break;
}
}
parents.clear();
}
return depth;
}
public static Method getMostSpecificMethod(Method[] methods, Class<?>... parameters) {
List<Method> compatibleMethods = Arrays.stream(methods)
.filter(x -> isMethodCallable(x, parameters))
.toList();
if (compatibleMethods.size() == 0)
throw new IllegalArgumentException("There are no compatible Methods provided");
for (int i = 0; i < parameters.length; i++) {
final int currI = i;
Class<?>[] parameterTypes = compatibleMethods.stream()
.map(x -> x.getParameterTypes()[currI])
.toArray(x -> new Class[x]);
Class<?> mostSpecific = getMostSpecificClass(parameterTypes, parameters[i]);
compatibleMethods = compatibleMethods.stream()
.filter(x -> Objects.equals(x.getParameterTypes()[currI], mostSpecific))
.toList();
}
return compatibleMethods.getFirst();
}
public static Class<?> getMostSpecificClass(Class<?>[] classes, Class<?> base) {
int min = Integer.MAX_VALUE;
Class<?> currMostSpecific = null;
for (Class<?> currClass : classes) {
int currCastDepth = getCastDepth(base, currClass);
if (currCastDepth <= min) {
min = currCastDepth;
currMostSpecific = currClass;
}
}
return currMostSpecific;
}
public static boolean isMethodCallable(Method method, Class<?>... parameters) {
if (!Objects.equals(method.getParameterCount(), parameters.length))
return false;
Class<?>[] methodParams = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
if (!methodParams[i].isAssignableFrom(parameters[i]))
return false;
}
return true;
}
public static String getFullMethodName(Method method) {
return method.getDeclaringClass().getName() + "." + method.getName();
}
}

View File

@ -0,0 +1,55 @@
package net.tomatentum.marinara.util;
import org.slf4j.Marker;
import org.slf4j.event.Level;
import org.slf4j.helpers.LegacyAbstractLogger;
import org.slf4j.helpers.MessageFormatter;
public class SimpleLogger extends LegacyAbstractLogger {
private String name;
public SimpleLogger(String name) {
this.name = name;
}
@Override
public boolean isTraceEnabled() {
return true;
}
@Override
public boolean isDebugEnabled() {
return true;
}
@Override
public boolean isInfoEnabled() {
return true;
}
@Override
public boolean isWarnEnabled() {
return true;
}
@Override
public boolean isErrorEnabled() {
return true;
}
@Override
protected String getFullyQualifiedCallerName() {
return this.name;
}
@Override
protected void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments,
Throwable throwable) {
String formatted = MessageFormatter.basicArrayFormat(messagePattern, arguments);
System.out.println("[%s] %s => %s".formatted(level, this.name, formatted));
}
}

View File

@ -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 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;
import net.tomatentum.marinara.util.LoggerUtil;
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 = LoggerUtil.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);
}
}

View File

@ -0,0 +1,51 @@
package net.tomatentum.marinara.wrapper;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.util.LoggerUtil;
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 = LoggerUtil.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) -> 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)
.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);
}
}

View File

@ -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);
}

View File

@ -0,0 +1,72 @@
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 io.leangen.geantyref.GenericTypeReflector;
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public class IdentifierProvider {
public static IdentifierProvider of(Converter<?>... converter) {
return new IdentifierProvider(Arrays.asList(converter));
}
private Map<Class<?>, Converter<?>> converter;
private Logger logger = LoggerUtil.getLogger(getClass());
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);
return conv.convert(context);
}
@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);
}
}
}

View File

@ -3,23 +3,16 @@ package net.tomatentum.marinara.wrapper;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType;
import net.tomatentum.marinara.interaction.InteractionType;
public abstract class LibraryWrapper {
private List<Consumer<Object>> interactionSubscriber;
protected LibraryWrapper() {
interactionSubscriber = new ArrayList<>();
this.interactionSubscriber = new ArrayList<>();
}
public void handleInteraction(Object context) {
interactionSubscriber.forEach((o) -> o.accept(context));
interactionSubscriber.forEach(o -> o.accept(context));
}
public void subscribeInteractions(Consumer<Object> consumer) {
@ -29,12 +22,9 @@ public abstract class LibraryWrapper {
interactionSubscriber.remove(consumer);
}
public abstract InteractionType getInteractionType(Class<?> clazz);
public abstract CommandRegisterer<?> getRegisterer();
public abstract IdentifierProvider createIdentifierProvider();
public abstract ContextObjectProvider getContextObjectProvider();
public abstract void respondAutocomplete(Object context, List<Object> options);
public abstract void registerSlashCommands(SlashCommandDefinition[] defs);
public abstract Object convertCommandOption(Object context, SlashCommandOptionType type, String optionName);
public abstract ExecutableSlashCommandDefinition getCommandDefinition(Object context);
public abstract String getButtonId(Object context);
public abstract Object getComponentContextObject(Object context, Class<?> type);
}

View File

@ -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;
}
}

View File

@ -13,5 +13,9 @@ plugins {
rootProject.name = "Marinara"
include(":lib")
include(":wrapper-javacord")
include(":wrapper-discord4j")
project(":wrapper-javacord").projectDir = file("wrapper/javacord")
project(":wrapper-discord4j").projectDir = file("wrapper/discord4j")

View File

@ -0,0 +1,43 @@
/*
* 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`
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)
testImplementation(libs.mockito)
testImplementation(libs.discord4j)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
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()
}

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,86 @@
package net.tomatentum.marinara.wrapper.discord4j;
import java.util.List;
import java.util.function.UnaryOperator;
import org.slf4j.Logger;
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.util.LoggerUtil;
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 = LoggerUtil.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);
}
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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.getRegistry().addInteractions(new TestAutocomplete());
wrapper.handleInteraction(autoCompleteEventMock);
verify(autoCompleteEventMock).respondWithSuggestions(any());
}
}

View File

@ -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)
public class ButtonTest {
@Test
public 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.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(buttonEventMock);
assertTrue(TestButton.didRun);
}
}

View File

@ -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)
public class InteractionCheckTest {
@Test
public void testInteractionCheck() {
ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("test");
LibraryWrapper wrapper = new Discord4JWrapper(null);
Marinara marinara = Marinara.load(wrapper);
marinara.getCheckRegistry().addCheck(new TestInteractionCheck());
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(buttonEventMock);
assertTrue(TestInteractionCheck.preExecuted);
assertTrue(TestInteractionCheck.postExecuted);
assertTrue(TestButton.didRun);
}
@Test
public 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.getCheckRegistry().addCheck(new PermissionCheck());
marinara.getRegistry().addInteractions(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);
}
}

View File

@ -0,0 +1,74 @@
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)
public class SlashCommandTest {
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.getRegistry().addInteractions(new TestCommand());
marinara.getRegistry().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.getRegistry().addInteractions(new TestCommand());
wrapper.handleInteraction(eventMock);
}
}

View File

@ -0,0 +1,40 @@
package net.tomatentum.marinara.test.discord4j;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Collections;
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")
)
)
@AutoComplete("testAuto")
public void exec(ChatInputInteractionEvent context) {
}
@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()
};
}
}

View File

@ -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!");
}
}

View File

@ -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;
}
}

View File

@ -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!");
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -18,9 +18,10 @@ repositories {
dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)
testImplementation(libs.mockito)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation(libs.log4j)
implementation(libs.slf4j)
implementation(libs.javacord)
implementation(libs.geantyref)
implementation(project(":lib"))
@ -29,7 +30,7 @@ dependencies {
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
languageVersion = JavaLanguageVersion.of(23)
}
}

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -1,186 +1,70 @@
package net.tomatentum.marinara.wrapper.javacord;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.javacord.api.DiscordApi;
import org.javacord.api.interaction.ApplicationCommandInteraction;
import org.javacord.api.interaction.ButtonInteraction;
import org.javacord.api.interaction.AutocompleteInteraction;
import org.javacord.api.interaction.SlashCommandBuilder;
import org.javacord.api.interaction.SlashCommandInteraction;
import org.javacord.api.interaction.SlashCommandInteractionOption;
import org.javacord.api.interaction.SlashCommandOptionChoice;
import org.slf4j.Logger;
import io.leangen.geantyref.AnnotationFormatException;
import io.leangen.geantyref.TypeFactory;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType;
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.util.LoggerUtil;
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 DiscordApi api;
private JavacordContextObjectProvider contextObjectProvider;
private CommandRegisterer<SlashCommandBuilder> commandRegisterer;
private Logger logger = LoggerUtil.getLogger(getClass());
public JavacordWrapper(DiscordApi api) {
this.api = api;
api.addInteractionCreateListener((e) -> handleInteraction(e.getInteraction()));
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 InteractionType getInteractionType(Class<?> clazz) {
if (ApplicationCommandInteraction.class.isAssignableFrom(clazz))
return InteractionType.COMMAND;
if (ButtonInteraction.class.isAssignableFrom(clazz))
return InteractionType.BUTTON;
return null;
public CommandRegisterer<?> getRegisterer() {
return this.commandRegisterer;
}
@Override
public void registerSlashCommands(SlashCommandDefinition[] defs) {
HashMap<Long, Set<SlashCommandBuilder>> serverCommands = new HashMap<>();
Set<SlashCommandBuilder> globalCommands = new HashSet<>();
for (SlashCommandDefinition slashCommandDefinition : defs) {
SlashCommandBuilder builder = convertSlashCommand(slashCommandDefinition);
if (slashCommandDefinition.getFullSlashCommand().serverIds().length > 0) {
for (long serverId : slashCommandDefinition.getFullSlashCommand().serverIds()) {
serverCommands.putIfAbsent(serverId, new HashSet<>());
serverCommands.get(serverId).add(builder);
}
}else
globalCommands.add(builder);
}
for (long serverId : serverCommands.keySet()) {
api.bulkOverwriteServerApplicationCommands(serverId, serverCommands.get(serverId));
}
api.bulkOverwriteGlobalApplicationCommands(globalCommands);
public IdentifierProvider createIdentifierProvider() {
return IdentifierProvider.of(
new SlashCommandIdentifierConverter(),
new AutocompleteIdentifierConverter(),
new ButtonIdentifierConverter()
);
}
@Override
public Object convertCommandOption(Object context, SlashCommandOptionType type, String optionName) {
if (!(context instanceof SlashCommandInteraction))
return null;
SlashCommandInteraction interaction = (SlashCommandInteraction) context;
if (!interaction.getArguments().isEmpty())
return getOptionValue(interaction.getOptionByName(optionName).get(), type);
SlashCommandInteractionOption subCommandOption = interaction.getOptions().getFirst();
if (!subCommandOption.getOptions().isEmpty())
subCommandOption = subCommandOption.getOptions().getFirst();
return getOptionValue(subCommandOption.getOptionByName(optionName).get(), type);
public ContextObjectProvider getContextObjectProvider() {
return contextObjectProvider;
}
@Override
public ExecutableSlashCommandDefinition getCommandDefinition(Object context) {
if (!(context instanceof SlashCommandInteraction))
return null;
SlashCommandInteraction interaction = (SlashCommandInteraction) context;
ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder();
List<SlashCommandInteractionOption> options = interaction.getOptions();
try {
builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", interaction.getCommandName())));
if (!options.getFirst().getArguments().isEmpty()) {
builder.setSubCommandGroup(TypeFactory.annotation(SubCommandGroup.class, Map.of("name", options.getFirst().getName())));
builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getOptions().getFirst().getName())));
}else
builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getName())));
} catch (AnnotationFormatException e) {
e.printStackTrace();
}
return builder.build();
}
private SlashCommandBuilder convertSlashCommand(SlashCommandDefinition def) {
List<org.javacord.api.interaction.SlashCommandOption> options = new ArrayList<>();
SlashCommand cmd = def.getFullSlashCommand();
if (!def.isRootCommand()) {
Arrays.stream(def.getSubCommands(null)).map(this::convertSubCommandDef).forEach(options::add);
Arrays.stream(def.getSubCommandGroups()).map((x) -> convertSubCommandGroupDef(def, x)).forEach(options::add);
}else {
Arrays.stream(cmd.options()).map(this::convertOptionDef).forEach(options::add);
}
return org.javacord.api.interaction.SlashCommand.with(cmd.name(), cmd.description(), options);
}
private org.javacord.api.interaction.SlashCommandOption convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) {
SubCommand[] subCommands = def.getSubCommands(subGroup.name());
org.javacord.api.interaction.SlashCommandOption[] convertedSubCommands = (org.javacord.api.interaction.SlashCommandOption[]) Arrays.stream(subCommands).map(this::convertSubCommandDef).toArray();
return org.javacord.api.interaction.SlashCommandOption.createWithOptions(org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND_GROUP, subGroup.name(), subGroup.description(), Arrays.asList(convertedSubCommands));
}
private org.javacord.api.interaction.SlashCommandOption convertSubCommandDef(SubCommand sub) {
List<org.javacord.api.interaction.SlashCommandOption> convertedOptions = new ArrayList<>();
Arrays.stream(sub.options()).map(this::convertOptionDef).forEach(convertedOptions::add);
return org.javacord.api.interaction.SlashCommandOption.createWithOptions(org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND, sub.name(), sub.description(), convertedOptions);
}
private org.javacord.api.interaction.SlashCommandOption convertOptionDef(SlashCommandOption option) {
org.javacord.api.interaction.SlashCommandOptionType type = Enum.valueOf(org.javacord.api.interaction.SlashCommandOptionType.class, option.type().toString());
return org.javacord.api.interaction.SlashCommandOption.create(type, option.name(), option.description(), option.required());
}
private Object getOptionValue(SlashCommandInteractionOption option, SlashCommandOptionType type) {
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.getMentionableValue().get();
case ROLE:
return option.getRoleValue().get();
case STRING:
return option.getStringValue().get();
case USER:
return option.getUserValue().get();
default:
return null;
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);
}
}
@Override
public String getButtonId(Object context) {
ButtonInteraction button = (ButtonInteraction) context;
return button.getCustomId();
}
@Override
public Object getComponentContextObject(Object context, Class<?> type) {
ButtonInteraction button = (ButtonInteraction) context;
switch (type.getName()) {
case "org.javacord.api.entity.channel.TextChannel":
return button.getChannel().orElse(null);
case "org.javacord.api.entity.message.Message":
return button.getMessage();
case "org.javacord.api.entity.server.Server":
return button.getServer().orElse(null);
case "org.javacord.api.entity.user.User":
return button.getUser();
}
return null;
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
public class AutoCompleteTest {
@Test
public 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.getRegistry().addInteractions(new TestAutocomplete());
wrapper.handleInteraction(autocompleteInteractionMock);
verify(autocompleteInteractionMock).respondWithChoices(any());
}
}

View File

@ -1,4 +1,4 @@
package net.tomatentum.marinara.test;
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -7,8 +7,7 @@ import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.test.mocks.ButtonInteractionMock;
import net.tomatentum.marinara.test.mocks.DiscordApiMock;
import net.tomatentum.marinara.test.javacord.mocks.CommonMocks;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper;
@ -17,10 +16,10 @@ public class ButtonTest {
@Test
public void testButtonExecution() {
LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test.
LibraryWrapper wrapper = new JavacordWrapper(null); //null okay as we don't use the discord API in this test.
Marinara marinara = Marinara.load(wrapper);
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(new ButtonInteractionMock());
wrapper.handleInteraction(CommonMocks.getButtonInteractionMock("test"));
assertTrue(TestButton.didRun);
}

View File

@ -0,0 +1,53 @@
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.javacord.api.entity.server.Server;
import org.javacord.api.interaction.ButtonInteraction;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.test.javacord.mocks.CommonMocks;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper;
import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck;
@TestInstance(Lifecycle.PER_CLASS)
public class InteractionCheckTest {
@Test
public void testInteractionCheck() {
LibraryWrapper wrapper = new JavacordWrapper(null);
Marinara marinara = Marinara.load(wrapper);
marinara.getCheckRegistry().addCheck(new TestInteractionCheck());
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(CommonMocks.getButtonInteractionMock("test"));
assertTrue(TestInteractionCheck.preExecuted);
assertTrue(TestInteractionCheck.postExecuted);
}
@Test
public void testPermissionCheck() {
LibraryWrapper wrapper = new JavacordWrapper(null);
Marinara marinara = Marinara.load(wrapper);
marinara.getCheckRegistry().addCheck(new PermissionCheck());
marinara.getRegistry().addInteractions(new TestButton());
Server serverMock = mock();
ButtonInteraction buttonInteractionMock = CommonMocks.getButtonInteractionMock("permissionCheck", serverMock);
when(serverMock.hasPermissions(any(), any())).thenReturn(false);
wrapper.handleInteraction(buttonInteractionMock);
assertFalse(TestButton.didPermRun);
when(serverMock.hasPermissions(any(), any())).thenReturn(true);
wrapper.handleInteraction(buttonInteractionMock);
assertTrue(TestButton.didPermRun);
}
}

View File

@ -1,7 +1,15 @@
package net.tomatentum.marinara.test;
package net.tomatentum.marinara.test.javacord;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Optional;
import org.javacord.api.DiscordApi;
import org.javacord.api.DiscordApiBuilder;
import org.javacord.api.interaction.SlashCommandInteraction;
import org.javacord.api.interaction.SlashCommandInteractionOption;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -9,7 +17,6 @@ import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.test.mocks.SlashCommandInteractionMock;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper;
@TestInstance(Lifecycle.PER_CLASS)
@ -45,7 +52,18 @@ public class SlashCommandTest {
Marinara marinara = Marinara.load(wrapper);
marinara.getRegistry().addInteractions(new TestCommand());
wrapper.handleInteraction(new SlashCommandInteractionMock());
SlashCommandInteractionOption optionMock = mock();
SlashCommandInteraction interactionMock = mock();
when(optionMock.getName()).thenReturn("foo");
when(optionMock.getStringValue()).thenReturn(Optional.of("test"));
when(interactionMock.getCommandName()).thenReturn("test");
when(interactionMock.getOptions()).thenReturn(Arrays.asList(optionMock));
when(interactionMock.getArguments()).thenReturn(Arrays.asList(optionMock));
when(interactionMock.getOptionByName("foo")).thenReturn(Optional.of(optionMock));
wrapper.handleInteraction(interactionMock);
}

View File

@ -0,0 +1,39 @@
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Collections;
import org.javacord.api.event.interaction.SlashCommandCreateEvent;
import org.javacord.api.interaction.AutocompleteInteraction;
import org.javacord.api.interaction.SlashCommandInteraction;
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")
)
)
@AutoComplete("testAuto")
public void exec(SlashCommandInteraction context) {
}
@AutoComplete("testAuto")
public void autocomplete(AutocompleteInteraction context, String value) {
System.out.println("Success!");
assertEquals(value, "test");
context.respondWithChoices(Collections.emptyList());
}
}

View File

@ -1,21 +1,25 @@
package net.tomatentum.marinara.test;
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.Message;
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.entity.server.Server;
import org.javacord.api.entity.user.User;
import org.javacord.api.interaction.ButtonInteraction;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.test.javacord.TestInteractionCheck.TestCheck;
import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck.HasPermission;
public class TestButton implements InteractionHandler {
public static boolean didRun = false;
@Button("test")
@TestCheck
public void exec(ButtonInteraction interaction, TextChannel channel, Message message, User member, Server server) {
assertNotNull(interaction);
assertNotNull(channel);
@ -25,5 +29,14 @@ public class TestButton implements InteractionHandler {
didRun = true;
System.out.println("Success!");
}
public static boolean didPermRun = false;
@Button("permissionCheck")
@HasPermission({PermissionType.ADMINISTRATOR})
public void exec(ButtonInteraction interaction) {
didPermRun = true;
System.out.println("It worked!");
}
}

View File

@ -0,0 +1,20 @@
package net.tomatentum.marinara.test.javacord;
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;
}
}

View File

@ -1,10 +1,11 @@
package net.tomatentum.marinara.test;
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.javacord.api.interaction.SlashCommandInteraction;
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;
@ -20,7 +21,8 @@ public class TestCommand implements InteractionHandler {
@SlashCommandOption(
name = "foo",
description = "foo bar is very fooby",
type = SlashCommandOptionType.STRING
type = SlashCommandOptionType.STRING,
choices = @CommandChoices(cenum = TestChoiceEnum.class)
)
}
)

View File

@ -0,0 +1,37 @@
package net.tomatentum.marinara.test.javacord;
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;
}
}

View File

@ -0,0 +1,42 @@
package net.tomatentum.marinara.test.javacord.mocks;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Optional;
import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.Message;
import org.javacord.api.entity.server.Server;
import org.javacord.api.entity.user.User;
import org.javacord.api.interaction.ButtonInteraction;
public class CommonMocks {
public static ButtonInteraction getButtonInteractionMock(String customId) {
ButtonInteraction buttonInteractionMock = mock();
when(buttonInteractionMock.getCustomId()).thenReturn(customId);
when(buttonInteractionMock.getMessage()).thenReturn(mock(Message.class));
when(buttonInteractionMock.getServer()).thenReturn(Optional.of(mock(Server.class)));
when(buttonInteractionMock.getChannel()).thenReturn(Optional.of(mock(TextChannel.class)));
when(buttonInteractionMock.getUser()).thenReturn(mock(User.class));
return buttonInteractionMock;
}
public static ButtonInteraction getButtonInteractionMock(String customId, Server serverMock) {
ButtonInteraction buttonInteractionMock = mock();
when(buttonInteractionMock.getCustomId()).thenReturn(customId);
when(buttonInteractionMock.getMessage()).thenReturn(mock(Message.class));
when(buttonInteractionMock.getServer()).thenReturn(Optional.of(serverMock));
when(buttonInteractionMock.getChannel()).thenReturn(Optional.of(mock(TextChannel.class)));
when(buttonInteractionMock.getUser()).thenReturn(mock(User.class));
return buttonInteractionMock;
}
}

View File

@ -1,137 +0,0 @@
package net.tomatentum.marinara.test.mocks;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.javacord.api.DiscordApi;
import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.Message;
import org.javacord.api.entity.message.component.ComponentType;
import org.javacord.api.entity.message.component.HighLevelComponent;
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.entity.server.Server;
import org.javacord.api.entity.user.User;
import org.javacord.api.interaction.ButtonInteraction;
import org.javacord.api.interaction.DiscordLocale;
import org.javacord.api.interaction.InteractionType;
import org.javacord.api.interaction.callback.ComponentInteractionOriginalMessageUpdater;
import org.javacord.api.interaction.callback.InteractionFollowupMessageBuilder;
import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;
import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater;
public class ButtonInteractionMock implements ButtonInteraction {
@Override
public Message getMessage() {
return new MessageMock();
}
@Override
public String getCustomId() {
return "test";
}
@Override
public ComponentType getComponentType() {
return ComponentType.BUTTON;
}
@Override
public CompletableFuture<Void> acknowledge() {
throw new UnsupportedOperationException("Unimplemented method 'acknowledge'");
}
@Override
public ComponentInteractionOriginalMessageUpdater createOriginalMessageUpdater() {
throw new UnsupportedOperationException("Unimplemented method 'createOriginalMessageUpdater'");
}
@Override
public long getApplicationId() {
throw new UnsupportedOperationException("Unimplemented method 'getApplicationId'");
}
@Override
public InteractionType getType() {
return InteractionType.MESSAGE_COMPONENT;
}
@Override
public InteractionImmediateResponseBuilder createImmediateResponder() {
throw new UnsupportedOperationException("Unimplemented method 'createImmediateResponder'");
}
@Override
public CompletableFuture<InteractionOriginalResponseUpdater> respondLater() {
throw new UnsupportedOperationException("Unimplemented method 'respondLater'");
}
@Override
public CompletableFuture<InteractionOriginalResponseUpdater> respondLater(boolean ephemeral) {
throw new UnsupportedOperationException("Unimplemented method 'respondLater'");
}
@Override
public CompletableFuture<Void> respondWithModal(String customId, String title,
List<HighLevelComponent> components) {
throw new UnsupportedOperationException("Unimplemented method 'respondWithModal'");
}
@Override
public InteractionFollowupMessageBuilder createFollowupMessageBuilder() {
throw new UnsupportedOperationException("Unimplemented method 'createFollowupMessageBuilder'");
}
@Override
public Optional<Server> getServer() {
return Optional.of(new ServerMock());
}
@Override
public Optional<TextChannel> getChannel() {
return Optional.of(new ChannelMock());
}
@Override
public User getUser() {
return new UserMock();
}
@Override
public String getToken() {
throw new UnsupportedOperationException("Unimplemented method 'getToken'");
}
@Override
public int getVersion() {
throw new UnsupportedOperationException("Unimplemented method 'getVersion'");
}
@Override
public DiscordLocale getLocale() {
throw new UnsupportedOperationException("Unimplemented method 'getLocale'");
}
@Override
public Optional<DiscordLocale> getServerLocale() {
throw new UnsupportedOperationException("Unimplemented method 'getServerLocale'");
}
@Override
public Optional<EnumSet<PermissionType>> getBotPermissions() {
throw new UnsupportedOperationException("Unimplemented method 'getBotPermissions'");
}
@Override
public DiscordApi getApi() {
throw new UnsupportedOperationException("Unimplemented method 'getApi'");
}
@Override
public long getId() {
throw new UnsupportedOperationException("Unimplemented method 'getId'");
}
}

View File

@ -1,824 +0,0 @@
package net.tomatentum.marinara.test.mocks;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.javacord.api.DiscordApi;
import org.javacord.api.entity.channel.ChannelType;
import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.Message;
import org.javacord.api.entity.message.MessageSet;
import org.javacord.api.entity.user.User;
import org.javacord.api.entity.webhook.IncomingWebhook;
import org.javacord.api.entity.webhook.Webhook;
import org.javacord.api.listener.ObjectAttachableListener;
import org.javacord.api.listener.channel.ChannelAttachableListener;
import org.javacord.api.listener.channel.ServerThreadChannelAttachableListener;
import org.javacord.api.listener.channel.TextChannelAttachableListener;
import org.javacord.api.listener.channel.server.thread.ServerThreadChannelCreateListener;
import org.javacord.api.listener.channel.server.thread.ServerThreadChannelDeleteListener;
import org.javacord.api.listener.channel.server.thread.ServerThreadChannelMembersUpdateListener;
import org.javacord.api.listener.channel.server.thread.ServerThreadChannelUpdateListener;
import org.javacord.api.listener.interaction.AutocompleteCreateListener;
import org.javacord.api.listener.interaction.ButtonClickListener;
import org.javacord.api.listener.interaction.InteractionCreateListener;
import org.javacord.api.listener.interaction.MessageComponentCreateListener;
import org.javacord.api.listener.interaction.MessageContextMenuCommandListener;
import org.javacord.api.listener.interaction.ModalSubmitListener;
import org.javacord.api.listener.interaction.SelectMenuChooseListener;
import org.javacord.api.listener.interaction.SlashCommandCreateListener;
import org.javacord.api.listener.interaction.UserContextMenuCommandListener;
import org.javacord.api.listener.message.CachedMessagePinListener;
import org.javacord.api.listener.message.CachedMessageUnpinListener;
import org.javacord.api.listener.message.ChannelPinsUpdateListener;
import org.javacord.api.listener.message.MessageCreateListener;
import org.javacord.api.listener.message.MessageDeleteListener;
import org.javacord.api.listener.message.MessageEditListener;
import org.javacord.api.listener.message.MessageReplyListener;
import org.javacord.api.listener.message.reaction.ReactionAddListener;
import org.javacord.api.listener.message.reaction.ReactionRemoveAllListener;
import org.javacord.api.listener.message.reaction.ReactionRemoveListener;
import org.javacord.api.listener.server.thread.ServerPrivateThreadJoinListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeArchiveTimestampListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeArchivedListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeAutoArchiveDurationListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeInvitableListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeLastMessageIdListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeLockedListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeMemberCountListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeMessageCountListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeRateLimitPerUserListener;
import org.javacord.api.listener.server.thread.ServerThreadChannelChangeTotalMessageSentListener;
import org.javacord.api.listener.user.UserStartTypingListener;
import org.javacord.api.util.cache.MessageCache;
import org.javacord.api.util.event.ListenerManager;
public class ChannelMock implements TextChannel {
@Override
public ChannelType getType() {
throw new UnsupportedOperationException("Unimplemented method 'getType'");
}
@Override
public DiscordApi getApi() {
throw new UnsupportedOperationException("Unimplemented method 'getApi'");
}
@Override
public long getId() {
throw new UnsupportedOperationException("Unimplemented method 'getId'");
}
@Override
public <T extends ChannelAttachableListener & ObjectAttachableListener> Collection<ListenerManager<T>> addChannelAttachableListener(
T listener) {
throw new UnsupportedOperationException("Unimplemented method 'addChannelAttachableListener'");
}
@Override
public <T extends ChannelAttachableListener & ObjectAttachableListener> void removeChannelAttachableListener(
T listener) {
throw new UnsupportedOperationException("Unimplemented method 'removeChannelAttachableListener'");
}
@Override
public <T extends ChannelAttachableListener & ObjectAttachableListener> Map<T, List<Class<T>>> getChannelAttachableListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getChannelAttachableListeners'");
}
@Override
public <T extends ChannelAttachableListener & ObjectAttachableListener> void removeListener(Class<T> listenerClass,
T listener) {
throw new UnsupportedOperationException("Unimplemented method 'removeListener'");
}
@Override
public ListenerManager<InteractionCreateListener> addInteractionCreateListener(InteractionCreateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addInteractionCreateListener'");
}
@Override
public List<InteractionCreateListener> getInteractionCreateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getInteractionCreateListeners'");
}
@Override
public ListenerManager<SlashCommandCreateListener> addSlashCommandCreateListener(
SlashCommandCreateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addSlashCommandCreateListener'");
}
@Override
public List<SlashCommandCreateListener> getSlashCommandCreateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getSlashCommandCreateListeners'");
}
@Override
public ListenerManager<AutocompleteCreateListener> addAutocompleteCreateListener(
AutocompleteCreateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addAutocompleteCreateListener'");
}
@Override
public List<AutocompleteCreateListener> getAutocompleteCreateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getAutocompleteCreateListeners'");
}
@Override
public ListenerManager<ModalSubmitListener> addModalSubmitListener(ModalSubmitListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addModalSubmitListener'");
}
@Override
public List<ModalSubmitListener> getModalSubmitListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getModalSubmitListeners'");
}
@Override
public ListenerManager<MessageContextMenuCommandListener> addMessageContextMenuCommandListener(
MessageContextMenuCommandListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addMessageContextMenuCommandListener'");
}
@Override
public List<MessageContextMenuCommandListener> getMessageContextMenuCommandListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getMessageContextMenuCommandListeners'");
}
@Override
public ListenerManager<MessageComponentCreateListener> addMessageComponentCreateListener(
MessageComponentCreateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addMessageComponentCreateListener'");
}
@Override
public List<MessageComponentCreateListener> getMessageComponentCreateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getMessageComponentCreateListeners'");
}
@Override
public ListenerManager<UserContextMenuCommandListener> addUserContextMenuCommandListener(
UserContextMenuCommandListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addUserContextMenuCommandListener'");
}
@Override
public List<UserContextMenuCommandListener> getUserContextMenuCommandListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getUserContextMenuCommandListeners'");
}
@Override
public ListenerManager<SelectMenuChooseListener> addSelectMenuChooseListener(SelectMenuChooseListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addSelectMenuChooseListener'");
}
@Override
public List<SelectMenuChooseListener> getSelectMenuChooseListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getSelectMenuChooseListeners'");
}
@Override
public ListenerManager<ButtonClickListener> addButtonClickListener(ButtonClickListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addButtonClickListener'");
}
@Override
public List<ButtonClickListener> getButtonClickListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getButtonClickListeners'");
}
@Override
public ListenerManager<UserStartTypingListener> addUserStartTypingListener(UserStartTypingListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addUserStartTypingListener'");
}
@Override
public List<UserStartTypingListener> getUserStartTypingListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getUserStartTypingListeners'");
}
@Override
public ListenerManager<MessageEditListener> addMessageEditListener(MessageEditListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addMessageEditListener'");
}
@Override
public List<MessageEditListener> getMessageEditListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getMessageEditListeners'");
}
@Override
public ListenerManager<ChannelPinsUpdateListener> addChannelPinsUpdateListener(ChannelPinsUpdateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addChannelPinsUpdateListener'");
}
@Override
public List<ChannelPinsUpdateListener> getChannelPinsUpdateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getChannelPinsUpdateListeners'");
}
@Override
public ListenerManager<ReactionRemoveListener> addReactionRemoveListener(ReactionRemoveListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addReactionRemoveListener'");
}
@Override
public List<ReactionRemoveListener> getReactionRemoveListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getReactionRemoveListeners'");
}
@Override
public ListenerManager<ReactionAddListener> addReactionAddListener(ReactionAddListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addReactionAddListener'");
}
@Override
public List<ReactionAddListener> getReactionAddListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getReactionAddListeners'");
}
@Override
public ListenerManager<ReactionRemoveAllListener> addReactionRemoveAllListener(ReactionRemoveAllListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addReactionRemoveAllListener'");
}
@Override
public List<ReactionRemoveAllListener> getReactionRemoveAllListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getReactionRemoveAllListeners'");
}
@Override
public ListenerManager<MessageCreateListener> addMessageCreateListener(MessageCreateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addMessageCreateListener'");
}
@Override
public List<MessageCreateListener> getMessageCreateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getMessageCreateListeners'");
}
@Override
public ListenerManager<CachedMessageUnpinListener> addCachedMessageUnpinListener(
CachedMessageUnpinListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addCachedMessageUnpinListener'");
}
@Override
public List<CachedMessageUnpinListener> getCachedMessageUnpinListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getCachedMessageUnpinListeners'");
}
@Override
public ListenerManager<CachedMessagePinListener> addCachedMessagePinListener(CachedMessagePinListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addCachedMessagePinListener'");
}
@Override
public List<CachedMessagePinListener> getCachedMessagePinListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getCachedMessagePinListeners'");
}
@Override
public ListenerManager<MessageReplyListener> addMessageReplyListener(MessageReplyListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addMessageReplyListener'");
}
@Override
public List<MessageReplyListener> getMessageReplyListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getMessageReplyListeners'");
}
@Override
public ListenerManager<MessageDeleteListener> addMessageDeleteListener(MessageDeleteListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addMessageDeleteListener'");
}
@Override
public List<MessageDeleteListener> getMessageDeleteListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getMessageDeleteListeners'");
}
@Override
public <T extends TextChannelAttachableListener & ObjectAttachableListener> Collection<ListenerManager<? extends TextChannelAttachableListener>> addTextChannelAttachableListener(
T listener) {
throw new UnsupportedOperationException("Unimplemented method 'addTextChannelAttachableListener'");
}
@Override
public <T extends TextChannelAttachableListener & ObjectAttachableListener> void removeTextChannelAttachableListener(
T listener) {
throw new UnsupportedOperationException("Unimplemented method 'removeTextChannelAttachableListener'");
}
@Override
public <T extends TextChannelAttachableListener & ObjectAttachableListener> Map<T, List<Class<T>>> getTextChannelAttachableListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getTextChannelAttachableListeners'");
}
@Override
public <T extends TextChannelAttachableListener & ObjectAttachableListener> void removeListener(
Class<T> listenerClass, T listener) {
throw new UnsupportedOperationException("Unimplemented method 'removeListener'");
}
@Override
public ListenerManager<ServerThreadChannelChangeLastMessageIdListener> addServerThreadChannelChangeLastMessageIdListener(
ServerThreadChannelChangeLastMessageIdListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeLastMessageIdListener'");
}
@Override
public List<ServerThreadChannelChangeLastMessageIdListener> getServerThreadChannelChangeLastMessageIdListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeLastMessageIdListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeArchivedListener> addServerThreadChannelChangeArchivedListener(
ServerThreadChannelChangeArchivedListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeArchivedListener'");
}
@Override
public List<ServerThreadChannelChangeArchivedListener> getServerThreadChannelChangeArchivedListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeArchivedListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeMemberCountListener> addServerThreadChannelChangeMemberCountListener(
ServerThreadChannelChangeMemberCountListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeMemberCountListener'");
}
@Override
public List<ServerThreadChannelChangeMemberCountListener> getServerThreadChannelChangeMemberCountListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeMemberCountListeners'");
}
@Override
public ListenerManager<ServerPrivateThreadJoinListener> addServerPrivateThreadJoinListener(
ServerPrivateThreadJoinListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerPrivateThreadJoinListener'");
}
@Override
public List<ServerPrivateThreadJoinListener> getServerPrivateThreadJoinListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerPrivateThreadJoinListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeInvitableListener> addServerThreadChannelChangeInvitableListener(
ServerThreadChannelChangeInvitableListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeInvitableListener'");
}
@Override
public List<ServerThreadChannelChangeInvitableListener> getServerThreadChannelChangeInvitableListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeInvitableListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeAutoArchiveDurationListener> addServerThreadChannelChangeAutoArchiveDurationListener(
ServerThreadChannelChangeAutoArchiveDurationListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeAutoArchiveDurationListener'");
}
@Override
public List<ServerThreadChannelChangeAutoArchiveDurationListener> getServerThreadChannelChangeAutoArchiveDurationListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeAutoArchiveDurationListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeRateLimitPerUserListener> addServerThreadChannelChangeRateLimitPerUserListener(
ServerThreadChannelChangeRateLimitPerUserListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeRateLimitPerUserListener'");
}
@Override
public List<ServerThreadChannelChangeRateLimitPerUserListener> getServerThreadChannelChangeRateLimitPerUserListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeRateLimitPerUserListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeLockedListener> addServerThreadChannelChangeLockedListener(
ServerThreadChannelChangeLockedListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeLockedListener'");
}
@Override
public List<ServerThreadChannelChangeLockedListener> getServerThreadChannelChangeLockedListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeLockedListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeArchiveTimestampListener> addServerThreadChannelChangeArchiveTimestampListener(
ServerThreadChannelChangeArchiveTimestampListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeArchiveTimestampListener'");
}
@Override
public List<ServerThreadChannelChangeArchiveTimestampListener> getServerThreadChannelChangeArchiveTimestampListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeArchiveTimestampListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeTotalMessageSentListener> addServerThreadChannelChangeTotalMessageSentListener(
ServerThreadChannelChangeTotalMessageSentListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeTotalMessageSentListener'");
}
@Override
public List<ServerThreadChannelChangeTotalMessageSentListener> getServerThreadChannelChangeTotalMessageSentListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeTotalMessageSentListeners'");
}
@Override
public ListenerManager<ServerThreadChannelChangeMessageCountListener> addServerThreadChannelChangeMessageCountListener(
ServerThreadChannelChangeMessageCountListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelChangeMessageCountListener'");
}
@Override
public List<ServerThreadChannelChangeMessageCountListener> getServerThreadChannelChangeMessageCountListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelChangeMessageCountListeners'");
}
@Override
public ListenerManager<ServerThreadChannelUpdateListener> addServerThreadChannelUpdateListener(
ServerThreadChannelUpdateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelUpdateListener'");
}
@Override
public List<ServerThreadChannelUpdateListener> getServerThreadChannelUpdateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelUpdateListeners'");
}
@Override
public ListenerManager<ServerThreadChannelMembersUpdateListener> addServerThreadChannelMembersUpdateListener(
ServerThreadChannelMembersUpdateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelMembersUpdateListener'");
}
@Override
public List<ServerThreadChannelMembersUpdateListener> getServerThreadChannelMembersUpdateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelMembersUpdateListeners'");
}
@Override
public ListenerManager<ServerThreadChannelCreateListener> addServerThreadChannelCreateListener(
ServerThreadChannelCreateListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelCreateListener'");
}
@Override
public List<ServerThreadChannelCreateListener> getServerThreadChannelCreateListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelCreateListeners'");
}
@Override
public ListenerManager<ServerThreadChannelDeleteListener> addServerThreadChannelDeleteListener(
ServerThreadChannelDeleteListener listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelDeleteListener'");
}
@Override
public List<ServerThreadChannelDeleteListener> getServerThreadChannelDeleteListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelDeleteListeners'");
}
@Override
public <T extends ServerThreadChannelAttachableListener & ObjectAttachableListener> Collection<ListenerManager<T>> addServerThreadChannelAttachableListener(
T listener) {
throw new UnsupportedOperationException("Unimplemented method 'addServerThreadChannelAttachableListener'");
}
@Override
public <T extends ServerThreadChannelAttachableListener & ObjectAttachableListener> void removeServerThreadChannelAttachableListener(
T listener) {
throw new UnsupportedOperationException("Unimplemented method 'removeServerThreadChannelAttachableListener'");
}
@Override
public <T extends ServerThreadChannelAttachableListener & ObjectAttachableListener> Map<T, List<Class<T>>> getServerThreadChannelAttachableListeners() {
throw new UnsupportedOperationException("Unimplemented method 'getServerThreadChannelAttachableListeners'");
}
@Override
public <T extends ServerThreadChannelAttachableListener & ObjectAttachableListener> void removeListener(
Class<T> listenerClass, T listener) {
throw new UnsupportedOperationException("Unimplemented method 'removeListener'");
}
@Override
public CompletableFuture<Void> type() {
throw new UnsupportedOperationException("Unimplemented method 'type'");
}
@Override
public CompletableFuture<Void> bulkDelete(long... messageIds) {
throw new UnsupportedOperationException("Unimplemented method 'bulkDelete'");
}
@Override
public CompletableFuture<Message> getMessageById(long id) {
throw new UnsupportedOperationException("Unimplemented method 'getMessageById'");
}
@Override
public CompletableFuture<MessageSet> getPins() {
throw new UnsupportedOperationException("Unimplemented method 'getPins'");
}
@Override
public CompletableFuture<MessageSet> getMessages(int limit) {
throw new UnsupportedOperationException("Unimplemented method 'getMessages'");
}
@Override
public CompletableFuture<MessageSet> getMessagesUntil(Predicate<Message> condition) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesUntil'");
}
@Override
public CompletableFuture<MessageSet> getMessagesWhile(Predicate<Message> condition) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesWhile'");
}
@Override
public Stream<Message> getMessagesAsStream() {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAsStream'");
}
@Override
public CompletableFuture<MessageSet> getMessagesBefore(int limit, long before) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesBefore'");
}
@Override
public CompletableFuture<MessageSet> getMessagesBeforeUntil(Predicate<Message> condition, long before) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesBeforeUntil'");
}
@Override
public CompletableFuture<MessageSet> getMessagesBeforeWhile(Predicate<Message> condition, long before) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesBeforeWhile'");
}
@Override
public Stream<Message> getMessagesBeforeAsStream(long before) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesBeforeAsStream'");
}
@Override
public CompletableFuture<MessageSet> getMessagesAfter(int limit, long after) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAfter'");
}
@Override
public CompletableFuture<MessageSet> getMessagesAfterUntil(Predicate<Message> condition, long after) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAfterUntil'");
}
@Override
public CompletableFuture<MessageSet> getMessagesAfterWhile(Predicate<Message> condition, long after) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAfterWhile'");
}
@Override
public Stream<Message> getMessagesAfterAsStream(long after) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAfterAsStream'");
}
@Override
public CompletableFuture<MessageSet> getMessagesAround(int limit, long around) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAround'");
}
@Override
public CompletableFuture<MessageSet> getMessagesAroundUntil(Predicate<Message> condition, long around) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAroundUntil'");
}
@Override
public CompletableFuture<MessageSet> getMessagesAroundWhile(Predicate<Message> condition, long around) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAroundWhile'");
}
@Override
public Stream<Message> getMessagesAroundAsStream(long around) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesAroundAsStream'");
}
@Override
public CompletableFuture<MessageSet> getMessagesBetween(long from, long to) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesBetween'");
}
@Override
public CompletableFuture<MessageSet> getMessagesBetweenUntil(Predicate<Message> condition, long from, long to) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesBetweenUntil'");
}
@Override
public CompletableFuture<MessageSet> getMessagesBetweenWhile(Predicate<Message> condition, long from, long to) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesBetweenWhile'");
}
@Override
public Stream<Message> getMessagesBetweenAsStream(long from, long to) {
throw new UnsupportedOperationException("Unimplemented method 'getMessagesBetweenAsStream'");
}
@Override
public MessageCache getMessageCache() {
throw new UnsupportedOperationException("Unimplemented method 'getMessageCache'");
}
@Override
public CompletableFuture<List<Webhook>> getWebhooks() {
throw new UnsupportedOperationException("Unimplemented method 'getWebhooks'");
}
@Override
public CompletableFuture<List<Webhook>> getAllIncomingWebhooks() {
throw new UnsupportedOperationException("Unimplemented method 'getAllIncomingWebhooks'");
}
@Override
public CompletableFuture<List<IncomingWebhook>> getIncomingWebhooks() {
throw new UnsupportedOperationException("Unimplemented method 'getIncomingWebhooks'");
}
@Override
public boolean canWrite(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canWrite'");
}
@Override
public boolean canUseExternalEmojis(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canUseExternalEmojis'");
}
@Override
public boolean canEmbedLinks(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canEmbedLinks'");
}
@Override
public boolean canReadMessageHistory(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canReadMessageHistory'");
}
@Override
public boolean canUseTts(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canUseTts'");
}
@Override
public boolean canAttachFiles(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canAttachFiles'");
}
@Override
public boolean canAddNewReactions(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canAddNewReactions'");
}
@Override
public boolean canManageMessages(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canManageMessages'");
}
@Override
public boolean canMentionEveryone(User user) {
throw new UnsupportedOperationException("Unimplemented method 'canMentionEveryone'");
}
}

Some files were not shown because too many files have changed in this diff Show More