├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ └── from-spigot-forums.md
└── workflows
│ └── gradle.yml
├── .gitignore
├── .idea
└── icon.png
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── java
└── com
│ └── cyr1en
│ └── commandprompter
│ ├── CommandPrompter.java
│ ├── PluginLogger.java
│ ├── PluginMessenger.java
│ ├── api
│ ├── Dispatcher.java
│ └── prompt
│ │ ├── CompoundableValidator.java
│ │ ├── InputValidator.java
│ │ └── Prompt.java
│ ├── commands
│ ├── CommandAPIWrapper.java
│ ├── DelegateCommand.java
│ └── MainCommand.java
│ ├── config
│ ├── AliasedSection.java
│ ├── CommandPrompterConfig.java
│ ├── ConfigurationManager.java
│ ├── PromptConfig.java
│ ├── annotations
│ │ ├── field
│ │ │ ├── ConfigNode.java
│ │ │ ├── IntegerConstraint.java
│ │ │ ├── Match.java
│ │ │ ├── NodeComment.java
│ │ │ ├── NodeDefault.java
│ │ │ └── NodeName.java
│ │ └── type
│ │ │ ├── ConfigHeader.java
│ │ │ ├── ConfigPath.java
│ │ │ └── Configuration.java
│ └── handlers
│ │ ├── ConfigTypeHandler.java
│ │ ├── ConfigTypeHandlerFactory.java
│ │ └── impl
│ │ ├── BooleanHandler.java
│ │ ├── DoubleHandler.java
│ │ ├── IntegerHandler.java
│ │ ├── ListHandler.java
│ │ └── StringHandler.java
│ ├── hook
│ ├── Hook.java
│ ├── HookContainer.java
│ ├── annotations
│ │ └── TargetPlugin.java
│ └── hooks
│ │ ├── BaseHook.java
│ │ ├── CarbonChatHook.java
│ │ ├── FilterHook.java
│ │ ├── HuskTownsHook.java
│ │ ├── LuckPermsHook.java
│ │ ├── PapiHook.java
│ │ ├── PremiumVanishHook.java
│ │ ├── SuperVanishHook.java
│ │ ├── TownyHook.java
│ │ ├── VanishHook.java
│ │ └── VanishNoPacketHook.java
│ ├── listener
│ ├── CommandListener.java
│ ├── CommandSendListener.java
│ └── VanillaListener.java
│ ├── prompt
│ ├── ContextProcessor.java
│ ├── PromptContext.java
│ ├── PromptManager.java
│ ├── PromptParser.java
│ ├── PromptQueue.java
│ ├── PromptRegistry.java
│ ├── prompts
│ │ ├── AbstractPrompt.java
│ │ ├── AnvilPrompt.java
│ │ ├── ChatPrompt.java
│ │ ├── PlayerUIPrompt.java
│ │ └── SignPrompt.java
│ ├── ui
│ │ ├── CacheFilter.java
│ │ ├── HeadCache.java
│ │ └── inventory
│ │ │ └── ControlPane.java
│ └── validators
│ │ ├── CompoundedValidator.java
│ │ ├── JSExprValidator.java
│ │ ├── NoopValidator.java
│ │ ├── OnlinePlayerValidator.java
│ │ └── RegexValidator.java
│ └── util
│ ├── MMUtil.java
│ ├── ServerUtil.java
│ ├── Util.java
│ └── unsafe
│ ├── PvtFieldMutationException.java
│ └── PvtFieldMutator.java
└── resources
├── CommandPrompter.properties
└── plugin.yml
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | .
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to CommandPrompter
2 |
3 | We welcome contributions from the community! Please take a moment to review this document in order to make the contribution process smooth and effective for everyone involved.
4 |
5 | ## Bug Reports and Feature Requests
6 |
7 | If you encounter any issues or would like to request a new feature, please open an issue on the [GitHub issue tracker](https://github.com/CyR1en/CommandPrompter/issues) with a descriptive title and detailed information about the problem or feature request.
8 |
9 | ## Pull Requests
10 |
11 | We encourage you to contribute to CommandPrompter by submitting pull requests for any bug fixes, improvements, or new features. Here's a simple guide to help you get started:
12 |
13 | 1. Fork the repository and create your branch from `main`.
14 | 2. If you've added code that should be tested, please add tests.
15 | 3. Ensure your code adheres to the coding conventions used in the project.
16 | 4. Ensure that your commits are properly documented.
17 | 5. Make sure to update the README with any changes if necessary.
18 |
19 | ## Coding Standards
20 |
21 | Please follow the coding standards used in the project, including:
22 |
23 | - Use clear and descriptive variable and function names.
24 | - Write clear and concise comments where necessary.
25 | - Adhere to the existing code formatting style.
26 |
27 | ## License
28 |
29 | By contributing to CommandPrompter, you agree that your contributions will be licensed under the [MIT License](https://opensource.org/licenses/MIT).
30 |
31 | ## Contact
32 |
33 | If you have any further questions or need more information, feel free to contact us via [email](mailto:cyrien3519@gmail.com).
34 |
35 | Thank you for your interest in contributing to CommandPrompter!
36 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: cyr1en
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/from-spigot-forums.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: From Spigot Forums
3 | about: Issues or features that were proposed on Spigot Forums.
4 | title: ''
5 | labels: ''
6 | assignees: CyR1en
7 |
8 | ---
9 |
10 | This issue/feature-request was posted in [Spigot](discussion link), but moved here so we can keep track of it.
11 |
12 | # Content
13 | ```
14 | [paste content here]
15 | ```
16 |
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | name: Java CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | release:
9 | types:
10 | - created
11 |
12 | jobs:
13 | build:
14 | if: github.event_name != 'release'
15 |
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2.4.0
20 | - name: Set up JDK
21 | uses: actions/setup-java@v2.5.0
22 | with:
23 | distribution: temurin
24 | java-version: 21
25 | - name: Build with Gradle
26 | run: ./gradlew clean build
27 | - name: Upload artifact
28 | uses: actions/upload-artifact@v4.4.3
29 | with:
30 | name: CommandPrompter
31 | path: build/libs
32 |
33 | publish:
34 | if: github.event_name == 'release'
35 |
36 | runs-on: ubuntu-latest
37 |
38 | steps:
39 | - uses: actions/checkout@v2.4.0
40 | - name: Set up JDK
41 | uses: actions/setup-java@v2.5.0
42 | with:
43 | distribution: temurin
44 | java-version: 21
45 | - name: Publish to Kakuno
46 | env:
47 | KAKUNO_USER: ${{ secrets.KAKUNO_USER }}
48 | KAKUNO_TOKEN: ${{ secrets.KAKUNO_TOKEN }}
49 | run: ./gradlew clean build publish
50 |
51 | - name: Export LATEST_TAG
52 | run: |
53 | echo "LATEST_TAG=$(curl -qsSL \
54 | -H "Accept: application/vnd.github+json" \
55 | -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
56 | -H "X-GitHub-Api-Version: 2022-11-28" \
57 | "${{ github.api_url }}/repos/${{ github.repository }}/releases/latest" \
58 | | jq -r .tag_name)" >> $GITHUB_ENV
59 | - name: Upload Release Artifact
60 | env:
61 | GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
62 | run:
63 | gh release upload ${{ env.LATEST_TAG }} build/libs/*.jar
64 |
65 |
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Gradle template
3 | .gradle
4 | /build/
5 |
6 | # Ignore Gradle GUI config
7 | gradle-app.setting
8 |
9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
10 | !gradle-wrapper.jar
11 |
12 | # Cache of project
13 | .gradletasknamecache
14 |
15 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
16 | # gradle/wrapper/gradle-wrapper.properties
17 | ### macOS template
18 | # General
19 | .DS_Store
20 | .AppleDouble
21 | .LSOverride
22 |
23 | # Icon must end with two \r
24 | Icon
25 |
26 | # Thumbnails
27 | ._*
28 |
29 | # Files that might appear in the root of a volume
30 | .DocumentRevisions-V100
31 | .fseventsd
32 | .Spotlight-V100
33 | .TemporaryItems
34 | .Trashes
35 | .VolumeIcon.icns
36 | .com.apple.timemachine.donotpresent
37 |
38 | # Directories potentially created on remote AFP share
39 | .AppleDB
40 | .AppleDesktop
41 | Network Trash Folder
42 | Temporary Items
43 | .apdisk
44 | ### Windows template
45 | # Windows thumbnail cache files
46 | Thumbs.db
47 | ehthumbs.db
48 | ehthumbs_vista.db
49 |
50 | # Dump file
51 | *.stackdump
52 |
53 | # Folder config file
54 | [Dd]esktop.ini
55 |
56 | # Recycle Bin used on file shares
57 | $RECYCLE.BIN/
58 |
59 | # Windows Installer files
60 | *.cab
61 | *.msi
62 | *.msix
63 | *.msm
64 | *.msp
65 |
66 | # Windows shortcuts
67 | *.lnk
68 | ### Example user template template
69 | ### Example user template
70 |
71 | # IntelliJ project files
72 | .idea
73 | *.iml
74 | out
75 | gen### Java template
76 |
77 | # Vscode
78 | .vscode
79 |
80 | # Compiled class file
81 | *.class
82 |
83 | # Log file
84 | *.log
85 |
86 | # BlueJ files
87 | *.ctxt
88 |
89 | # Mobile Tools for Java (J2ME)
90 | .mtj.tmp/
91 |
92 | # Package Files #
93 | *.jar
94 | *.war
95 | *.nar
96 | *.ear
97 | *.zip
98 | *.tar.gz
99 | *.rar
100 |
101 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
102 | hs_err_pid*
103 |
104 | bin/
--------------------------------------------------------------------------------
/.idea/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CyR1en/CommandPrompter/d4761a82b0d6ec8c09f029ec18ee610f2e34372f/.idea/icon.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Ethan Bacurio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # CommandPrompter
3 | [](https://ci.cyr1en.com/job/CommandPrompter)
4 | [](https://cyr1en.gitbook.io/commandprompter/)
5 | [](https://discord.com/invite/qHM8kE4XHj)
6 | [](https://ko-fi.com/cyr1en)
7 |
8 | CommandPrompter is a powerful tool that enhances the command prompting experience and menu creation in your Minecraft server. With its customizable configuration options, CommandPrompter allows you to tailor the command prompting process to suit your server's specific requirements.
9 |
10 | Features:
11 | * **Easy to use** - designed to be used within minutes after installation.
12 | * **Multiple prompts** - allows you to choose different [prompts](https://cyr1en.gitbook.io/commandprompter/prompts/) for your menus.
13 | * **Multi language support** - supports utf-8 characters to support a wide range of languages.
14 | * **Input validations** - never worry about miss inputs anymore.
15 | * **Console delegate** - a robust way to prompt a player via console.
16 | * **Post command** - expands your command by incorporation post commands.
17 |
18 | ## Building
19 |
20 | CommandPrompter uses Gradle as a project manager. You can build CommandPrompter for yourself by following the instructions below:
21 |
22 | #### Requirements
23 | * JDK 17 or newer
24 | * Git
25 |
26 | #### Compiling from source
27 | ```sh
28 | git clone https://github.com/CyR1en/CommandPrompter.git
29 | cd CommandPrompter/
30 | ./gradlew clean build
31 | ```
32 |
33 | ## Special Thanks To:
34 |
120 | * If unsafe is enabled in the config, this plugin will use the modified
121 | * command map. Otherwise, it will just use the vanilla listener.
122 | */
123 | private void initCommandListener() {
124 | commandListener = new VanillaListener(promptManager);
125 | Bukkit.getPluginManager().registerEvents(commandListener, this);
126 | }
127 |
128 | private void setupConfig() {
129 | configManager = new ConfigurationManager(this);
130 | config = configManager.getConfig(CommandPrompterConfig.class);
131 | promptConfig = configManager.getConfig(PromptConfig.class);
132 | }
133 |
134 | private void setupCommands() {
135 | commandAPIWrapper.registerCommands();
136 | }
137 |
138 | private void setupUpdater() {
139 | updateChecker = new UpdateChecker(this, 47772);
140 | if (updateChecker.isDisabled())
141 | return;
142 | Bukkit.getServer().getScheduler().runTaskAsynchronously(this, () -> {
143 | if (updateChecker.newVersionAvailable())
144 | logger.info(SRegex.ANSI_GREEN + "A new update is available! (" +
145 | updateChecker.getCurrVersion().asString() + ")" + SRegex.ANSI_RESET);
146 | else
147 | logger.info("No update was found.");
148 | });
149 | Bukkit.getPluginManager().registerEvents(updateChecker, this);
150 | }
151 |
152 | public I18N getI18N() {
153 | return i18n;
154 | }
155 |
156 | public HookContainer getHookContainer() {
157 | return this.hookContainer;
158 | }
159 |
160 | public PluginMessenger getMessenger() {
161 | return messenger;
162 | }
163 |
164 | public PromptManager getPromptManager() {
165 | return promptManager;
166 | }
167 |
168 | public PluginLogger getPluginLogger() {
169 | return logger;
170 | }
171 |
172 | public HeadCache getHeadCache() {
173 | return headCache;
174 | }
175 |
176 | public void reload(boolean clean) {
177 | config = configManager.reload(CommandPrompterConfig.class);
178 | promptConfig = configManager.reload(PromptConfig.class);
179 | messenger.setPrefix(config.promptPrefix());
180 | logger = new PluginLogger(this, "CommandPrompter");
181 | i18n = new I18N(this, "CommandPrompter");
182 | headCache.reBuildCache();
183 | promptManager.getParser().initRegex();
184 | ChatPrompt.DefaultListener.setPriority(this);
185 | setupUpdater();
186 | if (clean)
187 | promptManager.clearPromptRegistry();
188 |
189 | // Update commands just in case tab completer is changed
190 | Bukkit.getOnlinePlayers().forEach(Player::updateCommands);
191 | }
192 |
193 | public static CommandPrompter getInstance() {
194 | return instance;
195 | }
196 |
197 | public CommandPrompterConfig getConfiguration() {
198 | return config;
199 | }
200 |
201 | public PromptConfig getPromptConfig() {
202 | return promptConfig;
203 | }
204 |
205 | public UpdateChecker getUpdateChecker() {
206 | return updateChecker;
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/PluginLogger.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter;
2 |
3 | import org.bukkit.Bukkit;
4 | import org.fusesource.jansi.Ansi;
5 |
6 | import java.awt.*;
7 | import java.util.Objects;
8 | import java.util.UnknownFormatConversionException;
9 | import java.util.logging.Level;
10 |
11 | public class PluginLogger {
12 |
13 | private String prefix;
14 | private String debugPrefix;
15 |
16 | private final ColorGradient normalGrad;
17 | private final ColorGradient debugGrad;
18 |
19 | private final boolean debugMode;
20 | private final boolean isFancy;
21 |
22 | public PluginLogger(CommandPrompter plugin, String prefix) {
23 | this.isFancy = plugin.getConfiguration().fancyLogger();
24 | this.debugMode = plugin.getConfiguration().debugMode();
25 |
26 | // Spread love not war <3
27 | normalGrad = new ColorGradient(new Color(1, 88, 181), new Color(246, 206, 0));
28 |
29 | debugGrad = new ColorGradient(new Color(255, 96, 109), new Color(255, 195, 113));
30 |
31 | setPrefix(prefix);
32 | }
33 |
34 | public void setPrefix(String prefix) {
35 | var sep = isFancy ? new Ansi().fgRgb(153, 214, 90).a(">>").reset().toString() : ">>";
36 | var normal = isFancy ? makeGradient(prefix, normalGrad) : prefix;
37 | var debug = isFancy ? makeGradient(prefix + "-" + "Debug", debugGrad) : prefix + "-" + "Debug";
38 | this.prefix = String.format("%s %s ", normal, sep);
39 | this.debugPrefix = String.format("%s %s ", debug, sep);
40 | }
41 |
42 | private String makeGradient(String prefix, ColorGradient grad) {
43 | var colorGrad = grad.getGradient(prefix.length());
44 | var a = new Ansi();
45 | for (int i = 0; i < colorGrad.length; i++)
46 | a.fgRgb(colorGrad[i].getRGB()).a(prefix.charAt(i));
47 |
48 | return a.reset().toString();
49 | }
50 |
51 | public void log(String prefix, Level level, String msg, Object... args) {
52 | String pre = prefix == null ? getPrefix() : prefix;
53 | try {
54 | if (msg.matches("%s"))
55 | msg = String.format(msg, args);
56 | } catch (UnknownFormatConversionException ignore) {
57 | }
58 | Bukkit.getLogger().log(level, pre + msg);
59 | }
60 |
61 | public void log(Level level, String msg, Object... args) {
62 | log(null, level, msg, args);
63 | }
64 |
65 | public void info(String msg, Object... args) {
66 | log(Level.INFO, msg, args);
67 | }
68 |
69 | public void warn(String msg, Object... args) {
70 | var str = new Ansi().fgRgb(255, 195, 113).a(msg).reset().toString();
71 | log(Level.WARNING, str, args);
72 | }
73 |
74 | public void err(String msg, Object... args) {
75 | var str = new Ansi().fgRgb(255, 50, 21).a(msg).reset().toString();
76 | log(Level.SEVERE, str, args);
77 | }
78 |
79 | private Class> lastDebugClass;
80 |
81 | public void debug(String msg, Object... args) {
82 | var caller = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass();
83 | var callerAvailable = Objects.nonNull(caller) && !caller.getSimpleName().isBlank();
84 | if (callerAvailable)
85 | lastDebugClass = caller;
86 |
87 | if (debugMode) {
88 | msg = callerAvailable ? String.format("[%s] - %s", caller.getSimpleName(), msg)
89 | : Objects.isNull(lastDebugClass) ? msg
90 | : String.format("[%s?] - %s", lastDebugClass.getSimpleName(), msg);
91 | var str = new Ansi().fgRgb(255, 195, 113).a(msg).reset().toString();
92 | log(debugPrefix, Level.INFO, str, args);
93 | }
94 | }
95 |
96 | private String getPrefix() {
97 | return prefix;
98 | }
99 |
100 | public record ColorGradient(Color c1, Color c2) {
101 |
102 | public Color[] getGradient(int segmentCount) {
103 | var colors = new Color[segmentCount];
104 | var seg = 1.0F / segmentCount;
105 | var currSeg = 0.0F;
106 | for (int i = 0; i < segmentCount; i++) {
107 | colors[i] = getPercentGradient(currSeg);
108 | currSeg += seg;
109 | }
110 | return colors;
111 | }
112 |
113 | public Color getPercentGradient(float percent) {
114 | if (percent < 0 || percent > 1)
115 | return Color.WHITE;
116 | return new Color(
117 | linInterpolate(c1.getRed(), c2.getRed(), percent),
118 | linInterpolate(c1.getGreen(), c2.getGreen(), percent),
119 | linInterpolate(c1.getBlue(), c2.getBlue(), percent));
120 | }
121 |
122 | private int linInterpolate(int f1, int f2, float percent) {
123 | var res = f1 + percent * (f2 - f1);
124 | return Math.round(res);
125 | }
126 |
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/PluginMessenger.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter;
2 |
3 | import com.cyr1en.commandprompter.util.Util;
4 | import org.bukkit.command.CommandSender;
5 |
6 | public class PluginMessenger {
7 |
8 | private String prefix;
9 |
10 | public PluginMessenger(String prefix) {
11 | this.prefix = prefix;
12 | }
13 |
14 | public void setPrefix(String prefix) {
15 | this.prefix = prefix;
16 | }
17 |
18 | public void sendMessage(CommandSender sender, String message) {
19 | if (message.isBlank()) return;
20 | var whole = prefix + message;
21 | sender.sendMessage(Util.color(whole));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/api/Dispatcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Ethan Bacurio
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package com.cyr1en.commandprompter.api;
26 |
27 | import com.cyr1en.commandprompter.CommandPrompter;
28 | import org.bukkit.Bukkit;
29 | import org.bukkit.command.CommandSender;
30 | import org.bukkit.entity.Player;
31 | import org.bukkit.plugin.Plugin;
32 | import org.bukkit.scheduler.BukkitRunnable;
33 | import org.jetbrains.annotations.NotNull;
34 |
35 | /**
36 | * Player command dispatcher for Support with CommandPrompter.
37 | *
38 | *
39 | * Because CommandPrompter cannot catch commands that were dispatched from
40 | * {@link org.bukkit.Bukkit#dispatchCommand(CommandSender, String)}, plugins
41 | * need a special way to execute player commands.
42 | *
43 | */
44 | public class Dispatcher {
45 |
46 | /**
47 | * Dispatches command by forcing a player to chat the command.
48 | * This will allow plugins to support CommandPrompter.
49 | *
50 | * @param plugin Instance of plugin.
51 | * @param sender command sender (in menu's, then the item clicker)
52 | * @param command command that would be dispatched.
53 | */
54 | public static void dispatchCommand(Plugin plugin, Player sender, String command) {
55 | final String checked = command.codePointAt(0) == 0x2F ? command : "/" + command;
56 | new BukkitRunnable() {
57 | public void run() {
58 | sender.chat(checked);
59 | }
60 | }.runTask(plugin);
61 | }
62 |
63 | /**
64 | * Dispatch the command as Console.
65 | *
66 | * @param command command that would be dispatched.
67 | */
68 | public static void dispatchConsole(final String command) {
69 | final String checked = command.codePointAt(0) == 0x2F ? command.substring(1) : command;
70 | new BukkitRunnable() {
71 | public void run() {
72 | Bukkit.dispatchCommand(Bukkit.getConsoleSender(), checked);
73 | }
74 | }.runTask(CommandPrompter.getInstance());
75 | }
76 |
77 | /**
78 | * Dispatch a command for a player with a PermissionAttachment that contains
79 | * all the whitelisted commands.
80 | *
81 | * @param plugin Instance of plugin.
82 | * @param sender command sender (in menu's, then the item clicker)
83 | * @param command command that would be dispatched.
84 | * @param ticks Number of ticks before the attachment expires
85 | * @param perms Permissions to set to the PermissionAttachment
86 | */
87 | public static void dispatchWithAttachment(Plugin plugin, Player sender, String command, int ticks,
88 | @NotNull String[] perms) {
89 | var commandPrompter = (CommandPrompter) plugin;
90 | var logger = commandPrompter.getPluginLogger();
91 |
92 | logger.debug("Dispatching command with permission attachment");
93 |
94 | var attachment = sender.addAttachment(plugin, ticks);
95 | if (attachment == null) {
96 | logger.err("Unable to create PermissionAttachment for " + sender.getName());
97 | return;
98 | }
99 |
100 | for (String perm : perms) {
101 | logger.debug("Attached Perm: " + perm);
102 | attachment.setPermission(perm, true);
103 | }
104 | attachment.getPermissible().recalculatePermissions();
105 | final String checked = command.codePointAt(0) == 0x2F ? command.substring(1) : command;
106 | Bukkit.dispatchCommand(sender, checked);
107 | //dispatchCommand(plugin, sender, command);
108 | sender.removeAttachment(attachment);
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/api/prompt/CompoundableValidator.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.api.prompt;
2 |
3 |
4 | /**
5 | * A class that represents a compoundable validator.
6 | *
7 | * This class is used to represent a validator that can be combined with other validators.
8 | */
9 | public interface CompoundableValidator {
10 |
11 | /**
12 | * Get the type of the compoundable validator.
13 | *
14 | * @return the type of the compoundable validator.
15 | */
16 | public Type getType();
17 |
18 | /**
19 | * Set the type of the compoundable validator.
20 | *
21 | * @param type the type of the compoundable validator.
22 | */
23 | public void setType(Type type);
24 |
25 | public enum Type {
26 | AND,
27 | OR,
28 | DEFAULT
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/api/prompt/InputValidator.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.api.prompt;
2 |
3 |
4 | import org.bukkit.entity.Player;
5 |
6 | /**
7 | * A functional interface for validating input.
8 | *
9 | *
Implement this interface to create a custom validator for your prompt.
10 | */
11 | public interface InputValidator {
12 |
13 | /**
14 | * Validate the input.
15 | *
16 | * Return true if the input is valid, false otherwise.
17 | *
18 | * @param input the input to validate
19 | * @return true if the input is valid, false otherwise
20 | */
21 | boolean validate(String input);
22 |
23 | /**
24 | * Get the alias for this validator.
25 | *
26 | * The alias is used to identify the validator.
27 | *
28 | * @return the alias for this validator
29 | */
30 | String alias();
31 |
32 | /**
33 | * Get the message to send when validation fails.
34 | *
35 | * @return the message to send when validation fails
36 | */
37 | String messageOnFail();
38 |
39 | /**
40 | * Get the player that entered the input.
41 | */
42 | Player inputPlayer();
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/api/prompt/Prompt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Ethan Bacurio
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package com.cyr1en.commandprompter.api.prompt;
26 |
27 | import com.cyr1en.commandprompter.CommandPrompter;
28 | import com.cyr1en.commandprompter.prompt.PromptContext;
29 | import com.cyr1en.commandprompter.prompt.PromptManager;
30 | import com.cyr1en.commandprompter.prompt.PromptParser;
31 |
32 | import java.util.List;
33 |
34 | public interface Prompt {
35 |
36 | /**
37 | * Method that sends the prompt.
38 | */
39 | void sendPrompt();
40 |
41 | /**
42 | * Accessor for the {@link PromptContext}.
43 | *
44 | * {@link PromptContext} contains all the information that you need for
45 | * the {@link Prompt}.
46 | *
47 | * @return context for the prompt.
48 | * @see PromptContext
49 | */
50 | PromptContext getContext();
51 |
52 | /**
53 | * Get instance of the plugin.
54 | *
55 | * The instance is a sub-class {@link org.bukkit.plugin.java.JavaPlugin}.
56 | *
57 | * @return Instance of a plugin.
58 | */
59 | CommandPrompter getPlugin();
60 |
61 | /**
62 | * Get the actual prompt to send
63 | */
64 | String getPrompt();
65 |
66 | /**
67 | * Get prompt manager
68 | */
69 | PromptManager getPromptManager();
70 |
71 | List getArgs();
72 |
73 | /**
74 | * Set the input validator
75 | *
76 | * @param inputValidator input validator to check prompt input
77 | */
78 | void setInputValidator(InputValidator inputValidator);
79 |
80 | /**
81 | * Get the input validator
82 | *
83 | * @return input validator to check prompt input
84 | */
85 | InputValidator getInputValidator();
86 |
87 | /**
88 | * Returns a boolean value if inputs should be sanitized.
89 | *
90 | * @return true if inputs should be sanitized
91 | */
92 | boolean sanitizeInput();
93 |
94 | /**
95 | * Set whether inputs should be sanitized.
96 | *
97 | * @param sanitize true if inputs should be sanitized
98 | */
99 | void setInputSanitization(boolean sanitize);
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/commands/CommandAPIWrapper.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.commands;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.util.Util;
5 | import dev.jorel.commandapi.CommandAPI;
6 | import dev.jorel.commandapi.CommandAPIBukkitConfig;
7 |
8 | /**
9 | * Wrapper for CommandAPI.
10 | *
11 | *
12 | * Since CommandPrompter loads CommandAPI dynamically, we need a wrapper to
13 | * prevent
14 | * CommandAPI imports on the main class.
15 | */
16 | public class CommandAPIWrapper {
17 |
18 | private final CommandPrompter plugin;
19 |
20 | public CommandAPIWrapper(CommandPrompter plugin) {
21 | this.plugin = plugin;
22 | }
23 |
24 | public void load() {
25 | var config = new CommandAPIBukkitConfig(plugin);
26 | config.skipReloadDatapacks(true);
27 | config.useLatestNMSVersion(false);
28 | config = plugin.getConfiguration().debugMode() ? config.silentLogs(false).verboseOutput(true)
29 | : config.silentLogs(true).verboseOutput(false);
30 |
31 | var msg = plugin.getI18N().getProperty("DelegateConsoleOnly");
32 | config.missingExecutorImplementationMessage(Util.color(msg));
33 | CommandAPI.onLoad(config);
34 | }
35 |
36 | public void onEnable() {
37 | CommandAPI.onEnable();
38 | }
39 |
40 | public void onDisable() {
41 | CommandAPI.onDisable();
42 | }
43 |
44 | public void registerCommands() {
45 | new MainCommand(plugin).register();
46 | new DelegateCommand.ConsoleDelegate(plugin).register();
47 | new DelegateCommand.PlayerDelegate(plugin).register();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/commands/DelegateCommand.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.commands;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.PluginLogger;
5 | import com.cyr1en.commandprompter.PluginMessenger;
6 | import com.cyr1en.commandprompter.prompt.ContextProcessor;
7 | import com.cyr1en.commandprompter.prompt.PromptContext;
8 | import com.cyr1en.kiso.mc.I18N;
9 | import dev.jorel.commandapi.CommandAPICommand;
10 | import dev.jorel.commandapi.arguments.ArgumentSuggestions;
11 | import dev.jorel.commandapi.arguments.GreedyStringArgument;
12 | import dev.jorel.commandapi.arguments.PlayerArgument;
13 | import dev.jorel.commandapi.arguments.StringArgument;
14 | import dev.jorel.commandapi.executors.CommandArguments;
15 | import org.bukkit.command.CommandSender;
16 | import org.bukkit.command.ConsoleCommandSender;
17 | import org.bukkit.entity.Player;
18 | import org.bukkit.plugin.java.JavaPlugin;
19 |
20 | public abstract class DelegateCommand extends ContextProcessor {
21 |
22 | protected final CommandPrompter plugin;
23 | private final PluginLogger logger;
24 | private final PluginMessenger messenger;
25 | private final I18N i18n;
26 |
27 |
28 | private DelegateCommand(JavaPlugin plugin) {
29 | super((CommandPrompter) plugin, ((CommandPrompter) plugin).getPromptManager());
30 | this.plugin = (CommandPrompter) plugin;
31 | this.logger = this.plugin.getPluginLogger();
32 | this.messenger = this.plugin.getMessenger();
33 | this.i18n = this.plugin.getI18N();
34 | }
35 |
36 | public abstract void register();
37 |
38 | public abstract void doCommand(Player targetPlayer, String command, CommandArguments args);
39 |
40 | protected void exec(CommandSender sender, CommandArguments args) {
41 | logger.debug("Command Arguments: " + args.fullInput());
42 |
43 | if (!(sender instanceof ConsoleCommandSender)) {
44 | messenger.sendMessage(sender, i18n.getProperty("DelegateConsoleOnly"));
45 | return;
46 | }
47 |
48 | var arg0 = args.getRaw("target");
49 | if (arg0 == null || arg0.isEmpty()) return;
50 |
51 | var targetPlayer = plugin.getServer().getPlayer(arg0);
52 |
53 | if (targetPlayer == null) {
54 | messenger.sendMessage(sender, i18n.getFormattedProperty("DelegateInvalidPlayer", arg0));
55 | return;
56 | }
57 |
58 | var delegatedCommand = args.getRaw("command");
59 |
60 | if (delegatedCommand == null || delegatedCommand.isEmpty()) {
61 | messenger.sendMessage(sender, i18n.getProperty("DelegateInvalidCommand"));
62 | return;
63 | }
64 |
65 | if (delegatedCommand.contains("%target_player%"))
66 | delegatedCommand = delegatedCommand.replace("%target_player%", targetPlayer.getName());
67 |
68 | doCommand(targetPlayer, delegatedCommand, args);
69 | }
70 |
71 | public static class ConsoleDelegate extends DelegateCommand {
72 |
73 | ConsoleDelegate(JavaPlugin plugin) {
74 | super(plugin);
75 | }
76 |
77 | public void register() {
78 | new CommandAPICommand("consoledelegate")
79 | .withPermission("commandprompter.consoledelegate")
80 | .withArguments(new PlayerArgument("target"))
81 | .withArguments(new GreedyStringArgument("command"))
82 | .executesConsole(this::exec)
83 | .register();
84 | }
85 |
86 | public void doCommand(Player targetPlayer, String command, CommandArguments args) {
87 | var context = new PromptContext.Builder()
88 | .setSender(targetPlayer)
89 | .setContent(command)
90 | .setConsoleDelegate(true)
91 | .build();
92 | this.process(context);
93 | }
94 | }
95 |
96 | public static class PlayerDelegate extends DelegateCommand {
97 |
98 |
99 | PlayerDelegate(JavaPlugin plugin) {
100 | super(plugin);
101 | }
102 |
103 | @Override
104 | public void register() {
105 | var keys = plugin.getConfiguration().getPermissionKeys();
106 | ArgumentSuggestions suggestions = ArgumentSuggestions.strings(keys);
107 | var permissionArg = new StringArgument("permission")
108 | .replaceSuggestions(suggestions);
109 |
110 | new CommandAPICommand("playerdelegate")
111 | .withPermission("commandprompter.playerdelegate")
112 | .withArguments(new PlayerArgument("target"))
113 | .withArguments(permissionArg)
114 | .withArguments(new GreedyStringArgument("command"))
115 | .executesConsole(this::exec)
116 | .register();
117 | }
118 |
119 | @Override
120 | public void doCommand(Player targetPlayer, String command, CommandArguments args) {
121 | var context = new PromptContext.Builder()
122 | .setSender(targetPlayer)
123 | .setContent(command)
124 | .setPaKey(args.getRaw("permission"))
125 | .build();
126 | this.process(context);
127 | }
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/commands/MainCommand.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.commands;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import dev.jorel.commandapi.CommandAPICommand;
5 | import dev.jorel.commandapi.executors.CommandArguments;
6 | import org.bukkit.command.CommandSender;
7 |
8 | import java.util.Collections;
9 | import java.util.regex.Pattern;
10 |
11 | public class MainCommand {
12 | private final CommandPrompter plugin;
13 | // private final PluginLogger logger;
14 |
15 | public MainCommand(CommandPrompter plugin) {
16 | this.plugin = plugin;
17 | // this.logger = plugin.getPluginLogger();
18 | }
19 |
20 | public void register() {
21 | var command = new CommandAPICommand("commandprompter")
22 | .withAliases("cmdp")
23 | .withSubcommand(new Reload(plugin))
24 | .withSubcommand(new Cancel(plugin))
25 | .withSubcommand(new RebuildHeadCache(plugin));
26 |
27 | if (!plugin.getConfiguration().showCompleted())
28 | command.setArguments(Collections.emptyList());
29 |
30 | command.register();
31 | }
32 |
33 | public static class Reload extends CommandAPICommand {
34 |
35 | private final CommandPrompter plugin;
36 |
37 | public Reload(CommandPrompter plugin) {
38 | super("reload");
39 | this.plugin = plugin;
40 | withPermission("commandprompter.reload");
41 | executes(this::exec);
42 | }
43 |
44 | public void exec(CommandSender sender, CommandArguments args) {
45 | plugin.reload(true);
46 | String message = plugin.getI18N().getProperty("CommandReloadSuccess");
47 | plugin.getMessenger().sendMessage(sender, message);
48 | }
49 |
50 | }
51 |
52 | public static class Cancel extends CommandAPICommand {
53 |
54 | public static final Pattern commandPattern = Pattern.compile("(commandprompter|cmdp) cancel");
55 |
56 | private final CommandPrompter plugin;
57 |
58 | public Cancel(CommandPrompter plugin) {
59 | super("cancel");
60 | this.plugin = plugin;
61 | withPermission("commandprompter.cancel");
62 | executes(this::exec);
63 | }
64 |
65 | public void exec(CommandSender sender, CommandArguments args) {
66 | var inCommand = plugin.getPromptManager()
67 | .getPromptRegistry().inCommandProcess(sender);
68 | if (!inCommand) {
69 | var msg = plugin.getI18N().getProperty("CommandCancelNotInCompletion");
70 | plugin.getMessenger().sendMessage(sender, msg);
71 | return;
72 | }
73 |
74 | plugin.getPromptManager().cancel(sender);
75 | }
76 |
77 | }
78 |
79 | public static class RebuildHeadCache extends CommandAPICommand {
80 |
81 | private final CommandPrompter plugin;
82 |
83 | public RebuildHeadCache(CommandPrompter plugin) {
84 | super("rebuildheadcache");
85 | this.plugin = plugin;
86 | withAliases("rhc");
87 | withPermission("commandprompter.rebuildheadcache");
88 | executes(this::exec);
89 | }
90 |
91 | public void exec(CommandSender sender, CommandArguments args) {
92 | var currMillis = System.currentTimeMillis();
93 | plugin.getHeadCache().reBuildCache().thenAccept(c -> {
94 | var timeTaken = System.currentTimeMillis() - currMillis;
95 | var msg = plugin.getI18N().getFormattedProperty("CommandRebuildHeadCacheSuccess", timeTaken + "");
96 | plugin.getMessenger().sendMessage(sender, msg);
97 | plugin.getPluginLogger().debug("Rebuilt head cache in " + timeTaken + "ms");
98 | });
99 | }
100 |
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/AliasedSection.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config;
2 |
3 | import com.cyr1en.kiso.mc.configuration.base.Config;
4 | import org.bukkit.configuration.ConfigurationSection;
5 |
6 | public interface AliasedSection {
7 |
8 | Config rawConfig();
9 |
10 | /**
11 | * Get a value of a key in a validation section using a different key value to check if we're in the right section.
12 | *
13 | * @param key key to check
14 | * @param keyVal value of the key to check
15 | * @param query key to get the value of
16 | * @return value of the query key
17 | */
18 | default String getInputValidationValue(String section, String key, String keyVal, String query) {
19 | var raw = rawConfig();
20 | var validations = raw.getConfigurationSection(section);
21 |
22 | for (var k : validations.getKeys(false)) {
23 | var inner = validations.getConfigurationSection(k);
24 | var asserted = asserted(inner, key, keyVal, query);
25 | if (!asserted.isEmpty() && !asserted.isBlank()) return asserted;
26 | }
27 | return "";
28 | }
29 |
30 | default String asserted(ConfigurationSection section, String key, String keyVal, String query) {
31 | if (section == null) return "";
32 | // Still check for alias because we are anchoring each input validation with an alias.
33 | // Therefore, the alias must always be present.
34 | if (!section.getKeys(false).contains("Alias")) return "";
35 |
36 | var cfgAlias = section.getString(key);
37 | cfgAlias = cfgAlias != null ? cfgAlias : "";
38 |
39 | if (cfgAlias.equals(keyVal)) {
40 | var regex = section.getString(query);
41 | return regex != null ? regex : "";
42 | }
43 | return "";
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/CommandPrompterConfig.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config;
2 |
3 | import com.cyr1en.commandprompter.config.annotations.field.ConfigNode;
4 | import com.cyr1en.commandprompter.config.annotations.field.NodeComment;
5 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault;
6 | import com.cyr1en.commandprompter.config.annotations.field.NodeName;
7 | import com.cyr1en.commandprompter.config.annotations.type.ConfigHeader;
8 | import com.cyr1en.commandprompter.config.annotations.type.ConfigPath;
9 | import com.cyr1en.commandprompter.config.annotations.type.Configuration;
10 | import com.cyr1en.kiso.mc.configuration.base.Config;
11 |
12 | import java.util.List;
13 | import java.util.Set;
14 |
15 | @Configuration
16 | @ConfigPath("config.yml")
17 | @ConfigHeader({"Command Prompter", "Configuration"})
18 | public record CommandPrompterConfig(
19 | Config rawConfig,
20 |
21 | @ConfigNode
22 | @NodeName("Prompt-Prefix")
23 | @NodeDefault("[&3Prompter&r] ")
24 | @NodeComment({"Set the plugin prefix"})
25 | String promptPrefix,
26 |
27 | @ConfigNode
28 | @NodeName("Prompt-Timeout")
29 | @NodeDefault("300")
30 | @NodeComment({
31 | "After how many seconds until",
32 | "CommandPrompter cancels a",
33 | "prompt"})
34 | int promptTimeout,
35 |
36 | @ConfigNode
37 | @NodeName("Cancel-Keyword")
38 | @NodeDefault("cancel")
39 | @NodeComment({
40 | "Word that cancels command",
41 | "prompting."})
42 | String cancelKeyword,
43 |
44 | @ConfigNode
45 | @NodeName("Enable-Permission")
46 | @NodeDefault("false")
47 | @NodeComment({
48 | "Enable permission check",
49 | "before a player can use",
50 | "the prompting feature", "",
51 | "Checking for commandprompter.use"})
52 | boolean enablePermission,
53 |
54 | @ConfigNode
55 | @NodeName("Update-Checker")
56 | @NodeDefault("true")
57 | @NodeComment({
58 | "Allow CommandPrompter to",
59 | "check if it's up to date."})
60 | boolean updateChecker,
61 |
62 | @ConfigNode
63 | @NodeName("Argument-Regex")
64 | @NodeDefault("<.*?>")
65 | @NodeComment({
66 | "This will determine if",
67 | "a part of a command is",
68 | "a prompt.", "",
69 | "ONLY CHANGE THE FIRST AND LAST",
70 | "I.E (.*?), {.*?}, or [.*?]"})
71 | String argumentRegex,
72 |
73 | @ConfigNode
74 | @NodeName("Debug-Mode")
75 | @NodeDefault("false")
76 | @NodeComment({
77 | "Enable debug mode for CommandPrompter."
78 | })
79 | boolean debugMode,
80 |
81 | @ConfigNode
82 | @NodeName("Show-Complete-Command")
83 | @NodeDefault("true")
84 | @NodeComment({
85 | "Should CommandPrompter send",
86 | "the completed command to the",
87 | "player before dispatching it?"
88 | })
89 | boolean showCompleted,
90 |
91 | @ConfigNode
92 | @NodeName("Show-Prompt-Cancelled")
93 | @NodeDefault("true")
94 | @NodeComment({
95 | "Should CommandPrompter a",
96 | "prompt cancellation message",
97 | "to the player."
98 | })
99 | boolean showCancelled,
100 |
101 | @ConfigNode
102 | @NodeName("Fancy-Logger")
103 | @NodeDefault("true")
104 | @NodeComment({
105 | "Enable fancy logger",
106 | "Do /commandprompter reload to",
107 | "apply the change"
108 | })
109 | boolean fancyLogger,
110 |
111 | @ConfigNode
112 | @NodeName("Ignored-Commands")
113 | @NodeDefault("sampleCommand, sampleCommand2")
114 | @NodeComment({
115 | "What commands should CommandPrompter ignore",
116 | "",
117 | "When a command is ignored, CommandPrompter",
118 | "will not proceed to check if the command",
119 | "contains a prompt.",
120 | "",
121 | "Do not include the /",
122 | "",
123 | "VentureChat channels are automatically ignored."
124 | })
125 | List ignoredCommands,
126 |
127 | @ConfigNode
128 | @NodeName("Allowed-Commands-In-Prompt")
129 | @NodeDefault("sampleCommand, sampleCommand2")
130 | @NodeComment({
131 | "What commands should CommandPrompter allow",
132 | "",
133 | "When the player executes another command while",
134 | "completing a prompt.",
135 | "",
136 | "Do not include the /",
137 | "",
138 | })
139 | List allowedWhileInPrompt,
140 |
141 | @ConfigNode
142 | @NodeName("Permission-Attachment.ticks")
143 | @NodeDefault("1")
144 | @NodeComment({
145 | "Permission Attachment Config",
146 | "",
147 | "Permission attachments allow players",
148 | "to have temporary permissions.",
149 | "",
150 | "ticks - Set how long (in ticks) should the",
151 | " permission attachment persist.",
152 | "",
153 | "permissions - permissions to temporarily",
154 | " attach to the players."
155 | })
156 | int permissionAttachmentTicks,
157 |
158 | @ConfigNode
159 | @NodeName("Permission-Attachment.Permissions.GAMEMODE")
160 | @NodeDefault("bukkit.command.gamemode, " +
161 | "essentials.gamemode.survival," +
162 | "essentials.gamemode.creative")
163 | List attachmentPermissions,
164 |
165 | @ConfigNode
166 | @NodeName("Command-Tab-Complete")
167 | @NodeComment({
168 | "Enable command tab complete",
169 | "for CommandPrompter"
170 | })
171 | @NodeDefault("true")
172 | boolean commandTabComplete,
173 |
174 | @ConfigNode
175 | @NodeName("Ignore-MiniMessage")
176 | @NodeComment({
177 | "If Prompt-Regex is left to default",
178 | "this will ignore MiniMessage syntax."
179 | })
180 | boolean ignoreMiniMessage
181 |
182 |
183 | ) implements AliasedSection {
184 |
185 | public String[] getPermissionAttachment(String key) {
186 | var section = rawConfig.getConfigurationSection("Permission-Attachment.Permissions");
187 | var keyExist = section.getKeys(false).contains(key);
188 | return keyExist ? section.getStringList(key).toArray(String[]::new) : new String[]{};
189 | }
190 |
191 | public String[] getPermissionKeys() {
192 | var section = rawConfig.getConfigurationSection("Permission-Attachment.Permissions");
193 | Set keys = section == null ? Set.of() : section.getKeys(false);
194 | keys.add("NONE");
195 | return keys.toArray(String[]::new);
196 | }
197 |
198 |
199 | }
200 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/ConfigurationManager.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.PluginLogger;
5 | import com.cyr1en.commandprompter.config.annotations.field.*;
6 | import com.cyr1en.commandprompter.config.annotations.type.ConfigHeader;
7 | import com.cyr1en.commandprompter.config.annotations.type.ConfigPath;
8 | import com.cyr1en.commandprompter.config.annotations.type.Configuration;
9 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandlerFactory;
10 | import com.cyr1en.kiso.mc.configuration.base.Config;
11 | import com.cyr1en.kiso.mc.configuration.base.ConfigManager;
12 |
13 | import java.lang.reflect.Field;
14 | import java.lang.reflect.InvocationTargetException;
15 | import java.util.ArrayList;
16 | import java.util.regex.Pattern;
17 |
18 | /**
19 | * Class that manages your plugin's configuration(s)
20 | *
21 | *
22 | * This class will turn a configuration record into a YAML configuration file.
23 | * For this class to work, a record must be:
24 | * - Annotated with {@link Configuration}.
25 | * - Fields of the record must be annotated with {@link ConfigNode}
26 | *
27 | *
28 | * Reflection is heavily used in this class to grab the types of each field in a
29 | * record and initializes the defaults for the config file. It then uses reflection
30 | * again to query those default values in the config file to instantiate the actual
31 | * record with its respective data.
32 | */
33 | public class ConfigurationManager {
34 |
35 | private final ConfigManager configManager;
36 | private final PluginLogger logger;
37 |
38 | /**
39 | * Constructs a new ConfigurationManager for a given plugin.
40 | *
41 | * @param plugin The plugin instance which this manager will handle configurations for.
42 | */
43 | public ConfigurationManager(CommandPrompter plugin) {
44 | this.configManager = new ConfigManager(plugin);
45 | this.logger = plugin.getPluginLogger();
46 | }
47 |
48 | /**
49 | * Retrieves or initializes the configuration for a specified configuration class.
50 | *
51 | * @param The type of the configuration record.
52 | * @param configClass The class of the configuration record.
53 | * @return An instance of T with values from the configuration, or null if configuration class is not annotated properly.
54 | */
55 | public T getConfig(Class configClass) {
56 | if (configClass.getAnnotation(Configuration.class) == null)
57 | return null;
58 |
59 | var config = initConfigFile(configClass);
60 |
61 | initializeConfig(configClass, config);
62 |
63 | var configValues = new ArrayList<>();
64 | configValues.add(config);
65 |
66 | for (Field field : configClass.getDeclaredFields()) {
67 | if (field.getAnnotation(ConfigNode.class) == null) continue;
68 |
69 | var nameAnnotation = field.getAnnotation(NodeName.class);
70 | String nodeName = nameAnnotation != null ? nameAnnotation.value() : field.getName();
71 |
72 | var handler = ConfigTypeHandlerFactory.getHandler(field.getType());
73 |
74 | if (field.isAnnotationPresent(Match.class)) {
75 | var matchAnnotation = field.getAnnotation(Match.class);
76 | var regex = matchAnnotation.regex();
77 | var pattern = Pattern.compile(regex);
78 | var res = pattern.matcher(config.getString(nodeName)).matches();
79 | if (!res) {
80 | logger.warn("Configured value for " + nodeName + " is invalid! Using default.");
81 | configValues.add(handler.getDefault(field));
82 | continue;
83 | }
84 | }
85 |
86 | configValues.add(handler.getValue(config, nodeName, field));
87 | }
88 | try {
89 | // Since records in Java have canonical constructors, we directly use the first constructor
90 | var recordConfig = configClass.getDeclaredConstructors()[0].newInstance(configValues.toArray());
91 | @SuppressWarnings("unchecked") var out = (T) recordConfig;
92 | return out;
93 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
94 | logger.err("Failed to instantiate config: " + configClass.getSimpleName());
95 | }
96 | return null;
97 | }
98 |
99 | /**
100 | * Reloads the configuration for the given configuration class. This method simply calls getConfig.
101 | *
102 | * @param The type of the configuration record.
103 | * @param configClass The class of the configuration record to reload.
104 | * @return A reloaded instance of T or null if reload fails or configClass is invalid.
105 | */
106 | public T reload(Class configClass) {
107 | return getConfig(configClass);
108 | }
109 |
110 | /**
111 | * Initializes the configuration file with default values defined in the record.
112 | *
113 | * @param configClass The class of the record for which configuration needs to be initialized.
114 | * @param config The Config object representing the YAML file.
115 | */
116 | private void initializeConfig(Class> configClass, Config config) {
117 | var fields = configClass.getDeclaredFields();
118 | for (Field field : fields)
119 | initializeField(field, config);
120 | }
121 |
122 | /**
123 | * Initializes or retrieves the configuration file for the given configuration class.
124 | *
125 | * @param configClass The class of the configuration record.
126 | * @return A Config object representing the configuration file.
127 | */
128 | private Config initConfigFile(Class> configClass) {
129 | // Determine file path and header for the config file
130 | var pathAnnotation = configClass.getAnnotation(ConfigPath.class);
131 | var filePath = pathAnnotation == null ? configClass.getSimpleName() : pathAnnotation.value();
132 |
133 | var headerAnnotation = configClass.getAnnotation(ConfigHeader.class);
134 | var header = headerAnnotation == null ? new String[]{configClass.getSimpleName(), "Configuration"} :
135 | headerAnnotation.value();
136 | return configManager.getNewConfig(filePath, header);
137 | }
138 |
139 | /**
140 | * Initializes a single field of the configuration with its default value or annotated default.
141 | *
142 | * @param field The field from the record to initialize in the config.
143 | * @param config The Config object where the field should be initialized.
144 | */
145 | private void initializeField(Field field, Config config) {
146 | if (field.getAnnotation(ConfigNode.class) == null) return;
147 |
148 | var nameAnnotation = field.getAnnotation(NodeName.class);
149 | var nodeName = nameAnnotation != null ? nameAnnotation.value() : field.getName();
150 |
151 | var handler = ConfigTypeHandlerFactory.getHandler(field.getType());
152 |
153 | var defaultAnnotation = field.getAnnotation(NodeDefault.class);
154 | var nodeDefault = defaultAnnotation != null ? parseDefault(field) : handler.getDefault(field);
155 |
156 | var commentAnnotation = field.getAnnotation(NodeComment.class);
157 | var nodeComment = commentAnnotation != null ? commentAnnotation.value() : new String[]{};
158 |
159 | if (config.get(nodeName) == null) {
160 | handler.setValue(config, nodeName, nodeDefault, nodeComment);
161 | config.saveConfig();
162 | }
163 | }
164 |
165 | /**
166 | * Constructs a default value for a field when no default is provided via annotations.
167 | *
168 | * @param f The field for which to construct a default value.
169 | * @return An object representing the default value for the field, or null on failure.
170 | */
171 | private Object constructDefaultField(Field f) {
172 |
173 | try {
174 | if (f.getType().isPrimitive()) {
175 | var handler = ConfigTypeHandlerFactory.getHandler(f.getType());
176 | return handler.getDefault(f);
177 | }
178 | return f.getType().getDeclaredConstructor().newInstance();
179 | } catch (Exception e) {
180 | logger.err("Failed to instantiate default value for field: " + f.getName());
181 | }
182 | return null;
183 | }
184 |
185 | /**
186 | * Parses the default value from the @NodeDefault annotation for a field.
187 | *
188 | * @param field The field with a @NodeDefault annotation.
189 | * @return The parsed default value as an object, according to the field's type.
190 | */
191 | private Object parseDefault(Field field) {
192 | var handler = ConfigTypeHandlerFactory.getHandler(field.getType());
193 | return handler.getDefault(field);
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/field/ConfigNode.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.field;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.FIELD)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface ConfigNode {
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/field/IntegerConstraint.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.field;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.FIELD)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface IntegerConstraint {
11 |
12 | int min();
13 |
14 | int max();
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/field/Match.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.field;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.FIELD)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface Match {
11 | String regex();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/field/NodeComment.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.field;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.FIELD)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface NodeComment {
11 | String[] value();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/field/NodeDefault.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.field;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.FIELD)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface NodeDefault {
11 | String value();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/field/NodeName.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.field;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.FIELD)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface NodeName {
11 | String value();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/type/ConfigHeader.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.type;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(value = ElementType.TYPE)
9 | @Retention(value = RetentionPolicy.RUNTIME)
10 | public @interface ConfigHeader {
11 | String[] value();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/type/ConfigPath.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.type;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(value = ElementType.TYPE)
9 | @Retention(value = RetentionPolicy.RUNTIME)
10 | public @interface ConfigPath {
11 | String value();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/annotations/type/Configuration.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.annotations.type;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(value = ElementType.TYPE)
9 | @Retention(value = RetentionPolicy.RUNTIME)
10 | public @interface Configuration {}
11 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/handlers/ConfigTypeHandler.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.handlers;
2 |
3 | import com.cyr1en.kiso.mc.configuration.base.Config;
4 |
5 | import java.lang.reflect.Field;
6 |
7 | public interface ConfigTypeHandler {
8 | T getValue(Config config, String nodeName, Field field);
9 |
10 | void setValue(Config config, String nodeName, Object value, String[] comments);
11 |
12 | T getDefault(Field field);
13 | }
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/handlers/ConfigTypeHandlerFactory.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.handlers;
2 |
3 | import com.cyr1en.commandprompter.config.handlers.impl.*;
4 |
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | public class ConfigTypeHandlerFactory {
10 | private static final Map, ConfigTypeHandler>> handlers = new HashMap<>();
11 |
12 | static {
13 | handlers.put(int.class, new IntegerHandler());
14 | handlers.put(boolean.class, new BooleanHandler());
15 | handlers.put(String.class, new StringHandler());
16 | handlers.put(double.class, new DoubleHandler());
17 | handlers.put(List.class, new ListHandler());
18 | }
19 |
20 | public static ConfigTypeHandler> getHandler(Class> type) {
21 | if (handlers.containsKey(type)) {
22 | return handlers.get(type);
23 | }
24 | return handlers.getOrDefault(type, new StringHandler()); // Fallback handler
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/handlers/impl/BooleanHandler.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.handlers.impl;
2 |
3 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault;
4 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler;
5 | import com.cyr1en.kiso.mc.configuration.base.Config;
6 |
7 | import java.lang.reflect.Field;
8 |
9 | public class BooleanHandler implements ConfigTypeHandler {
10 |
11 |
12 | @Override
13 | public Boolean getValue(Config config, String nodeName, Field field) {
14 | return config.getBoolean(nodeName);
15 | }
16 |
17 | @Override
18 | public void setValue(Config config, String nodeName, Object value, String[] comments) {
19 | config.set(nodeName, value, comments);
20 | }
21 |
22 | @Override
23 | public Boolean getDefault(Field field) {
24 | var defaultAnnotation = field.getAnnotation(NodeDefault.class);
25 | return defaultAnnotation != null && Boolean.parseBoolean(defaultAnnotation.value());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/handlers/impl/DoubleHandler.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.handlers.impl;
2 |
3 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault;
4 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler;
5 | import com.cyr1en.kiso.mc.configuration.base.Config;
6 |
7 | import java.lang.reflect.Field;
8 |
9 | public class DoubleHandler implements ConfigTypeHandler {
10 | @Override
11 | public Double getValue(Config config, String nodeName, Field field) {
12 | return config.getDouble(nodeName);
13 | }
14 |
15 | @Override
16 | public void setValue(Config config, String nodeName, Object value, String[] comments) {
17 | config.set(nodeName, value, comments);
18 | }
19 |
20 | @Override
21 | public Double getDefault(Field field) {
22 | var defaultAnnotation = field.getAnnotation(NodeDefault.class);
23 | return defaultAnnotation != null ? Double.parseDouble(defaultAnnotation.value()) : 0.0;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/handlers/impl/IntegerHandler.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.handlers.impl;
2 |
3 | import com.cyr1en.commandprompter.config.annotations.field.IntegerConstraint;
4 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault;
5 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler;
6 | import com.cyr1en.kiso.mc.configuration.base.Config;
7 |
8 | import java.lang.reflect.Field;
9 |
10 | public class IntegerHandler implements ConfigTypeHandler {
11 | @Override
12 | public Integer getValue(Config config, String nodeName, Field field) {
13 | int val = config.getInt(nodeName);
14 | IntegerConstraint constraint = field.getAnnotation(IntegerConstraint.class);
15 | if (constraint != null)
16 | val = Math.min(Math.max(val, constraint.min()), constraint.max());
17 | return val;
18 | }
19 |
20 | @Override
21 | public void setValue(Config config, String nodeName, Object value, String[] comments) {
22 | config.set(nodeName, value, comments);
23 | }
24 |
25 | @Override
26 | public Integer getDefault(Field field) {
27 | var defaultAnnotation = field.getAnnotation(NodeDefault.class);
28 | return defaultAnnotation != null ? Integer.parseInt(defaultAnnotation.value()) : 0;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/handlers/impl/ListHandler.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.handlers.impl;
2 |
3 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault;
4 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler;
5 | import com.cyr1en.kiso.mc.configuration.base.Config;
6 |
7 | import java.lang.reflect.Field;
8 | import java.util.List;
9 |
10 | public class ListHandler implements ConfigTypeHandler> {
11 | @Override
12 | public List> getValue(Config config, String nodeName, Field field) {
13 | return config.getList(nodeName);
14 | }
15 |
16 | @Override
17 | public void setValue(Config config, String nodeName, Object value, String[] comments) {
18 | config.set(nodeName, value, comments);
19 | }
20 |
21 | @Override
22 | public List> getDefault(Field field) {
23 | var defaultAnnotation = field.getAnnotation(NodeDefault.class);
24 | if (defaultAnnotation != null) {
25 | return List.of(defaultAnnotation.value().split(","));
26 | }
27 | return List.of();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/config/handlers/impl/StringHandler.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.config.handlers.impl;
2 |
3 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault;
4 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler;
5 | import com.cyr1en.kiso.mc.configuration.base.Config;
6 |
7 | import java.lang.reflect.Field;
8 |
9 | public class StringHandler implements ConfigTypeHandler {
10 | @Override
11 | public String getValue(Config config, String nodeName, Field field) {
12 | return config.getString(nodeName);
13 | }
14 |
15 | @Override
16 | public void setValue(Config config, String nodeName, Object value, String[] comments) {
17 | config.set(nodeName, value, comments);
18 | }
19 |
20 | @Override
21 | public String getDefault(Field field) {
22 | var defaultAnnotation = field.getAnnotation(NodeDefault.class);
23 | return defaultAnnotation != null ? defaultAnnotation.value() : "";
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/Hook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook;
2 |
3 | import com.cyr1en.commandprompter.util.Util.ConsumerFallback;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.util.NoSuchElementException;
7 | import java.util.Objects;
8 | import java.util.function.Consumer;
9 |
10 | public final class Hook {
11 |
12 | private static final Hook> EMPTY = new Hook<>();
13 |
14 | private final T value;
15 | private String targetPlugin = "";
16 |
17 | private Hook() {
18 | this.value = null;
19 | }
20 |
21 | private Hook(T val) {
22 | this.value = val;
23 | }
24 |
25 | public static Hook of(T val) {
26 | return new Hook<>(val);
27 | }
28 |
29 | public static Hook empty() {
30 | @SuppressWarnings("unchecked") var t = (Hook) EMPTY;
31 | return t;
32 | }
33 |
34 | public T get() {
35 | if (value == null)
36 | throw new NoSuchElementException("No value present");
37 | return value;
38 | }
39 |
40 | public void setTargetPlugin(String targetPlugin) {
41 | this.targetPlugin = targetPlugin;
42 | }
43 |
44 | public String getTargetPluginName() {
45 | return targetPlugin;
46 | }
47 |
48 | /**
49 | * Accessor to check if the plugin is hooked.
50 | *
51 | * @return if this plugin hook is actually hooked.
52 | */
53 | public boolean isHooked() {
54 | return Objects.nonNull(value);
55 | }
56 |
57 | public ConsumerFallback ifHooked(@NotNull Consumer consumer) {
58 | return new ConsumerFallback<>(value, consumer, this::isHooked);
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return "Hook{" +
64 | "targetPlugin='" + targetPlugin + '\'' +
65 | '}';
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
5 | import com.cyr1en.commandprompter.hook.hooks.*;
6 | import org.bukkit.Bukkit;
7 | import org.bukkit.event.Listener;
8 | import org.bukkit.plugin.IllegalPluginAccessException;
9 | import org.fusesource.jansi.Ansi;
10 |
11 | import java.lang.reflect.InvocationTargetException;
12 | import java.util.HashMap;
13 | import java.util.List;
14 |
15 | public class HookContainer extends HashMap, Hook>> {
16 |
17 | private final CommandPrompter plugin;
18 |
19 | public HookContainer(CommandPrompter plugin) {
20 | this.plugin = plugin;
21 | }
22 |
23 | public void initHooks() {
24 | hook(PremiumVanishHook.class);
25 | hook(SuperVanishHook.class);
26 | hook(CarbonChatHook.class);
27 | hook(VanishNoPacketHook.class);
28 | hook(PapiHook.class);
29 | hook(TownyHook.class);
30 | hook(LuckPermsHook.class);
31 | hook(HuskTownsHook.class);
32 | }
33 |
34 | @Override
35 | public Hook> put(Class> key, Hook> value) {
36 |
37 | if (value.isHooked()) {
38 | var prefix = new Ansi().fgRgb(153, 214, 90).a("✓").reset();
39 | plugin.getPluginLogger().info(prefix + " " + key.getSimpleName() + " contained");
40 | }
41 |
42 | return super.put(key, value);
43 | }
44 |
45 | private void hook(Class pluginHook) {
46 | var instance = constructHook(pluginHook);
47 | this.put(pluginHook, instance);
48 | }
49 |
50 | private Hook constructHook(Class pluginHook) {
51 | plugin.getPluginLogger().debug("Constructing hook: " + pluginHook.getSimpleName());
52 | if (!pluginHook.isAnnotationPresent(TargetPlugin.class)) return Hook.empty();
53 |
54 | var targetPluginName = pluginHook.getAnnotation(TargetPlugin.class).pluginName();
55 | plugin.getPluginLogger().debug("Hook target plugin name: " + targetPluginName);
56 | var targetEnabled = Bukkit.getPluginManager().isPluginEnabled(targetPluginName);
57 | plugin.getPluginLogger().debug("Target enabled: " + targetEnabled);
58 | if (!targetEnabled) {
59 | plugin.getPluginLogger().debug(targetPluginName + " is not enabled. Could not hook.");
60 | return Hook.empty();
61 | }
62 |
63 | try {
64 | var constructor = pluginHook.getDeclaredConstructor(CommandPrompter.class);
65 | constructor.setAccessible(true);
66 | plugin.getPluginLogger().debug("Hook construct: " + constructor.getName());
67 | var instance = constructor.newInstance(plugin);
68 | if (instance instanceof Listener)
69 | plugin.getServer().getPluginManager().registerEvents((Listener) instance, plugin);
70 | plugin.getPluginLogger().debug("Hook instance: " + instance.getClass());
71 |
72 | var hook = Hook.of(instance);
73 | hook.setTargetPlugin(targetPluginName);
74 | return hook;
75 | } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException | InstantiationException |
76 | IllegalPluginAccessException e) {
77 | plugin.getPluginLogger().debug(e.toString());
78 | }
79 | return Hook.empty();
80 | }
81 |
82 | @SuppressWarnings("unchecked")
83 | public Hook getVanishHook() {
84 | return values().stream()
85 | .filter(hook -> hook.isHooked() && (hook.get() instanceof VanishHook))
86 | .map(hook -> (Hook) hook).findFirst().orElse(Hook.of(new VanishHook(plugin)));
87 | }
88 |
89 | public Hook getHook(Class hookClass) {
90 | @SuppressWarnings("unchecked") var t = (Hook) get(hookClass);
91 | if (t == null) return Hook.empty();
92 | return t;
93 | }
94 |
95 | @SuppressWarnings("unchecked")
96 | public List extends Hook extends FilterHook>> getFilterHooks() {
97 | return values().stream()
98 | .filter(hook -> hook.isHooked() && hook.get() instanceof FilterHook)
99 | .map(hook -> (Hook extends FilterHook>) hook)
100 | .toList();
101 | }
102 |
103 | public boolean isHooked(Class> hookClass) {
104 | return getHook(hookClass).isHooked();
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/annotations/TargetPlugin.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(value = ElementType.TYPE)
9 | @Retention(value = RetentionPolicy.RUNTIME)
10 | public @interface TargetPlugin {
11 | String pluginName();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/BaseHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 |
5 | public class BaseHook {
6 |
7 | private final CommandPrompter plugin;
8 |
9 | public BaseHook(CommandPrompter plugin) {
10 | this.plugin = plugin;
11 | }
12 |
13 | public CommandPrompter getPlugin() {
14 | return this.plugin;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/CarbonChatHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
5 | import com.cyr1en.commandprompter.prompt.PromptContext;
6 | import com.cyr1en.commandprompter.prompt.PromptManager;
7 | import net.draycia.carbon.api.CarbonChatProvider;
8 | import net.draycia.carbon.api.event.events.CarbonChatEvent;
9 | import net.kyori.adventure.text.Component;
10 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
11 | import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
12 | import org.bukkit.Bukkit;
13 | import org.bukkit.event.Listener;
14 |
15 | import java.util.Arrays;
16 | import java.util.Objects;
17 |
18 | @TargetPlugin(pluginName = "CarbonChat")
19 | public class CarbonChatHook extends BaseHook implements Listener {
20 | private final PromptManager promptManager;
21 |
22 | public CarbonChatHook(CommandPrompter plugin) {
23 | super(plugin);
24 | this.promptManager = plugin.getPromptManager();
25 | }
26 |
27 | public boolean subscribe() {
28 | var cc = CarbonChatProvider.carbonChat();
29 | if (cc == null) {
30 | getPlugin().getPluginLogger().warn("No CarbonChat was provided, using default chat listener...");
31 | return false;
32 | }
33 | cc.eventHandler().subscribe(CarbonChatEvent.class, -100, false, this::handle);
34 | return true;
35 | }
36 |
37 | public void handle(CarbonChatEvent event) {
38 | var player = Bukkit.getPlayer(event.sender().uuid());
39 | if (Objects.isNull(player) || !promptManager.getPromptRegistry().inCommandProcess(player))
40 | return;
41 | event.cancelled(true);
42 | event.recipients().clear();
43 | Arrays.stream(event.getClass().getDeclaredMethods()).forEach(method -> getPlugin().getPluginLogger().debug(method.toGenericString()));
44 | var msg = event.message();
45 | var serializedMsg = PlainTextComponentSerializer.plainText().serialize(msg);
46 | var cancel = getPlugin().getConfiguration().cancelKeyword();
47 |
48 | if (cancel.equalsIgnoreCase(serializedMsg)) {
49 | promptManager.cancel(player);
50 | event.message(Component.empty());
51 | return;
52 | }
53 |
54 | var queue = promptManager.getPromptRegistry().get(player);
55 | if (Objects.isNull(queue))
56 | return;
57 |
58 | var prompt = queue.peek();
59 | if (Objects.nonNull(prompt)) {
60 | var ds = LegacyComponentSerializer.legacyAmpersand().serialize(event.message());
61 | serializedMsg = prompt.sanitizeInput() ? ds : serializedMsg;
62 | }
63 | var ctx = new PromptContext.Builder().setSender(player).setContent(serializedMsg).build();
64 | Bukkit.getScheduler().runTask(getPlugin(), () -> promptManager.processPrompt(ctx));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/FilterHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.prompt.ui.HeadCache;
4 |
5 | /**
6 | * A hook that registers filters to the head cache.
7 | */
8 | public interface FilterHook {
9 |
10 | /**
11 | * Register filters to the head cache.
12 | *
13 | * @param headCache the head cache
14 | */
15 | void registerFilters(HeadCache headCache);
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/HuskTownsHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
5 | import com.cyr1en.commandprompter.prompt.ui.CacheFilter;
6 | import com.cyr1en.commandprompter.prompt.ui.HeadCache;
7 | import net.william278.husktowns.api.HuskTownsAPI;
8 | import net.william278.husktowns.user.User;
9 | import org.bukkit.Bukkit;
10 | import org.bukkit.entity.Player;
11 |
12 | import java.util.List;
13 | import java.util.Objects;
14 | import java.util.regex.Pattern;
15 |
16 | @TargetPlugin(pluginName = "HuskTowns")
17 | public class HuskTownsHook extends BaseHook implements FilterHook {
18 | private static final HuskTownsAPI huskTownsAPI = HuskTownsAPI.getInstance();
19 |
20 | public HuskTownsHook(CommandPrompter plugin) {
21 | super(plugin);
22 | }
23 |
24 | @Override
25 | public void registerFilters(HeadCache headCache) {
26 | headCache.registerFilter(new TownFilter());
27 | }
28 |
29 | /*
30 | * A PlayerUI filter that would filter all players that are in the same town as the relative player.
31 | */
32 | private static class TownFilter extends CacheFilter {
33 |
34 | public TownFilter() {
35 | super(Pattern.compile("ht"), "PlayerUI.Filter-Format.HuskTowns");
36 | }
37 |
38 | @Override
39 | public CacheFilter reConstruct(String promptKey) {
40 | return this;
41 | }
42 |
43 | @Override
44 | public List filter(Player relativePlayer) {
45 | var town = huskTownsAPI.getUserTown(User.of(relativePlayer.getUniqueId(), relativePlayer.getName()));
46 | if (town.isEmpty()) return List.of();
47 | if (town.get().town().getMembers().isEmpty()) return List.of();
48 | return town.get().town().getMembers().keySet().stream()
49 | .map(Bukkit::getPlayer)
50 | .filter(Objects::nonNull)
51 | .toList();
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.Hook;
5 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
6 | import com.cyr1en.commandprompter.prompt.ui.CacheFilter;
7 | import com.cyr1en.commandprompter.prompt.ui.HeadCache;
8 | import net.luckperms.api.LuckPerms;
9 | import org.bukkit.Bukkit;
10 | import org.bukkit.entity.Player;
11 | import org.bukkit.plugin.RegisteredServiceProvider;
12 |
13 | import java.util.List;
14 | import java.util.regex.Pattern;
15 |
16 | /**
17 | * Hook for LuckPerms plugin.
18 | *
19 | *
20 | * Main component of this hook are the cache filters for LuckPerms groups.
21 | */
22 | @TargetPlugin(pluginName = "LuckPerms")
23 | public class LuckPermsHook extends BaseHook implements FilterHook {
24 |
25 | private LuckPerms api;
26 |
27 | /**
28 | * Construct a new LuckPermsHook.
29 | *
30 | * @param plugin the plugin
31 | */
32 | public LuckPermsHook(CommandPrompter plugin) {
33 | super(plugin);
34 | RegisteredServiceProvider provider = plugin.getServer().getServicesManager().getRegistration(LuckPerms.class);
35 | if (provider != null) {
36 | this.api = provider.getProvider();
37 | } else {
38 | // If LuckPerms was loaded but the service provider was not found then we make sure that the container
39 | // is empty.
40 | plugin.getHookContainer().replace(LuckPermsHook.class, Hook.empty());
41 | }
42 | }
43 |
44 | /**
45 | * Register the cache filters for LuckPerms groups.
46 | *
47 | * @param cache the head cache.
48 | */
49 | public void registerFilters(HeadCache cache) {
50 | cache.registerFilter(new OwnGroupFilter(this));
51 | cache.registerFilter(new GroupFilter(this));
52 | }
53 |
54 | /**
55 | * Get all players that are in the same group as the relative player.
56 | *
57 | * @param groupName the group name
58 | * @return a list of players with the same group
59 | */
60 | @SuppressWarnings("unchecked")
61 | private List getPlayersWithGroup(String groupName) {
62 | if (api == null || groupName.isBlank()) return List.of();
63 | return (List) Bukkit.getOnlinePlayers().stream()
64 | .filter(p -> {
65 | var user = api.getUserManager().getUser(p.getName());
66 | if (user == null) return false;
67 | var group = user.getPrimaryGroup();
68 | return group.equals(groupName);
69 | }).toList();
70 | }
71 |
72 | /**
73 | * Get the LuckPerms API.
74 | *
75 | * @return the LuckPerms API
76 | */
77 | private LuckPerms getApi() {
78 | return api;
79 | }
80 |
81 |
82 | /**
83 | * A PlayerUI filter that would filter all players that are in the same group as the relative player.
84 | *
85 | *
86 | * This is a special case of {@link CacheFilter} where the regex key is 'og'.
87 | * This would filter all players that are in the same group as the relative player.
88 | */
89 | private static class OwnGroupFilter extends CacheFilter {
90 |
91 | private final LuckPermsHook hook;
92 |
93 | /**
94 | * Construct a new own group filter.
95 | *
96 | * @param hook the LuckPerms hook
97 | */
98 | public OwnGroupFilter(LuckPermsHook hook) {
99 | super(Pattern.compile("lpo"), "PlayerUI.Filter-Format.LuckPermsOwnGroup");
100 | this.hook = hook;
101 | }
102 |
103 | @Override
104 | public CacheFilter reConstruct(String promptKey) {
105 | // No need to re-construct
106 | return this;
107 | }
108 |
109 | /**
110 | * Filter all players that are in the same group as the relative player.
111 | *
112 | * @param relativePlayer the players to filter
113 | * @return a list of players with the same group
114 | */
115 | @Override
116 | public List filter(Player relativePlayer) {
117 | var user = hook.getApi().getUserManager().getUser(relativePlayer.getUniqueId());
118 | if (user == null) return List.of();
119 | var group = user.getPrimaryGroup();
120 | return hook.getPlayersWithGroup(group);
121 | }
122 | }
123 |
124 | /**
125 | * A PlayerUI filter that would filter all players based on the group name.
126 | *
127 | *
128 | * The regex for this filter is 'g(\S+)'. In regex capturing group 1 is the group
129 | * name that would be used to filter.
130 | */
131 | private static class GroupFilter extends CacheFilter {
132 |
133 | private final String groupName;
134 | private final LuckPermsHook hook;
135 |
136 | /**
137 | * Default construct a new group filter.
138 | *
139 | * @param hook the LuckPerms hook
140 | */
141 | public GroupFilter(LuckPermsHook hook) {
142 | this("", hook);
143 | }
144 |
145 | /**
146 | * Constructor to construct a new group filter with a group name.
147 | *
148 | *
149 | * This constructor will be used by the {@link #reConstruct(String)} function.
150 | *
151 | * @param groupName the group name that would be used to filter
152 | * @param hook the LuckPerms hook
153 | */
154 | public GroupFilter(String groupName, LuckPermsHook hook) {
155 | super(Pattern.compile("lpg(\\S+);"), "PlayerUI.Filter-Format.LuckPermsGroup", 1);
156 | this.groupName = groupName;
157 | this.hook = hook;
158 | }
159 |
160 | @Override
161 | public CacheFilter reConstruct(String promptKey) {
162 | var matcher = this.getRegexKey().matcher(promptKey);
163 | var found = matcher.find();
164 | var groupName = found ? matcher.group(1) : "";
165 | return new GroupFilter(groupName, hook);
166 | }
167 |
168 | @Override
169 | public List filter(Player relativePlayer) {
170 | var players = hook.getPlayersWithGroup(groupName);
171 | hook.getPlugin().getPluginLogger().debug("Players: %s", players);
172 | return players;
173 | }
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/PapiHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
5 | import me.clip.placeholderapi.PlaceholderAPI;
6 | import org.bukkit.entity.Player;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | @TargetPlugin(pluginName = "PlaceholderAPI")
10 | public class PapiHook extends BaseHook {
11 | public PapiHook(CommandPrompter plugin) {
12 | super(plugin);
13 | }
14 |
15 | public String setPlaceholder(@NotNull Player player, @NotNull String txt) {
16 | if (!papiPlaceholders(txt)) return txt;
17 | return PlaceholderAPI.setPlaceholders(player, txt);
18 | }
19 |
20 | public boolean papiPlaceholders(String str) {
21 | return PlaceholderAPI.containsPlaceholders(str);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/PremiumVanishHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
5 | import de.myzelyam.api.vanish.PlayerVanishStateChangeEvent;
6 | import de.myzelyam.api.vanish.VanishAPI;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.entity.Player;
9 | import org.bukkit.event.EventHandler;
10 | import org.bukkit.event.EventPriority;
11 | import org.bukkit.event.Listener;
12 |
13 | @TargetPlugin(pluginName = "PremiumVanish")
14 | public class PremiumVanishHook extends VanishHook implements Listener {
15 |
16 | public PremiumVanishHook(CommandPrompter plugin) {
17 | super(plugin);
18 | }
19 |
20 | @Override
21 | public boolean isInvisible(Player p) {
22 | return VanishAPI.isInvisible(p);
23 | }
24 |
25 | @EventHandler(priority = EventPriority.NORMAL)
26 | public void onStateChange(PlayerVanishStateChangeEvent e) {
27 | var player = Bukkit.getPlayer(e.getUUID());
28 | if (player == null) return;
29 | onStateChange(player, e::isVanishing);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/SuperVanishHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
5 | import de.myzelyam.api.vanish.PlayerVanishStateChangeEvent;
6 | import de.myzelyam.api.vanish.VanishAPI;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.entity.Player;
9 | import org.bukkit.event.EventHandler;
10 | import org.bukkit.event.EventPriority;
11 | import org.bukkit.event.Listener;
12 |
13 | @TargetPlugin(pluginName = "SuperVanish")
14 | public class SuperVanishHook extends VanishHook implements Listener {
15 |
16 | private SuperVanishHook(CommandPrompter plugin) {
17 | super(plugin);
18 | }
19 |
20 | public boolean isInvisible(Player p) {
21 | return VanishAPI.isInvisible(p);
22 | }
23 |
24 | @EventHandler(priority = EventPriority.NORMAL)
25 | public void onVisibilityStateChange(PlayerVanishStateChangeEvent e) {
26 | var player = Bukkit.getPlayer(e.getUUID());
27 | if (player == null) return;
28 | onStateChange(player, e::isVanishing);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
5 | import com.cyr1en.commandprompter.prompt.ui.CacheFilter;
6 | import com.cyr1en.commandprompter.prompt.ui.HeadCache;
7 | import com.palmergames.bukkit.towny.TownyAPI;
8 | import org.bukkit.Bukkit;
9 | import org.bukkit.entity.Player;
10 |
11 | import java.util.List;
12 | import java.util.Objects;
13 | import java.util.regex.Pattern;
14 |
15 | @TargetPlugin(pluginName = "Towny")
16 | public class TownyHook extends BaseHook implements FilterHook {
17 | public TownyHook(CommandPrompter plugin) {
18 | super(plugin);
19 | }
20 |
21 | public void registerFilters(HeadCache cache) {
22 | cache.registerFilter(new TownFilter());
23 | cache.registerFilter(new NationFilter());
24 | }
25 |
26 | /**
27 | * A PlayerUI filter that would filter all players that are in the same town as the relative player.
28 | */
29 | private static class TownFilter extends CacheFilter {
30 |
31 |
32 | public TownFilter() {
33 | super(Pattern.compile("tt"), "PlayerUI.Filter-Format.TownyTown");
34 | }
35 |
36 | @Override
37 | public CacheFilter reConstruct(String promptKey) {
38 | return this;
39 | }
40 |
41 | @Override
42 | public List filter(Player relativePlayer) {
43 | var town = TownyAPI.getInstance().getTown(relativePlayer);
44 | if (town == null) return List.of();
45 | return town.getResidents().stream()
46 | .map(r -> Bukkit.getPlayer(r.getName()))
47 | .filter(Objects::nonNull)
48 | .toList();
49 | }
50 | }
51 |
52 | /**
53 | * A PlayerUI filter that would filter all players that are in the same nation as the relative player.
54 | */
55 | private static class NationFilter extends CacheFilter {
56 |
57 | public NationFilter() {
58 | super(Pattern.compile("tn"), "PlayerUI.Filter-Format.TownyNation");
59 | }
60 |
61 | @Override
62 | public CacheFilter reConstruct(String promptKey) {
63 | return this;
64 | }
65 |
66 | @Override
67 | public List filter(Player relativePlayer) {
68 | var nation = TownyAPI.getInstance().getNation(relativePlayer);
69 | if (nation == null) return List.of();
70 | return nation.getResidents().stream()
71 | .map(r -> Bukkit.getPlayer(r.getName()))
72 | .filter(Objects::nonNull)
73 | .toList();
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/VanishHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.prompt.ui.HeadCache;
5 | import org.bukkit.entity.Player;
6 | import org.bukkit.metadata.MetadataValue;
7 |
8 | import java.util.Objects;
9 | import java.util.function.Supplier;
10 |
11 | public class VanishHook extends BaseHook {
12 |
13 | private final HeadCache headCache;
14 |
15 | public VanishHook(CommandPrompter plugin) {
16 | super(plugin);
17 | this.headCache = plugin.getHeadCache();
18 | }
19 |
20 | public boolean isInvisible(Player p) {
21 | var meta = p.getMetadata("vanished").stream().filter(MetadataValue::asBoolean).findAny();
22 | return meta.isPresent();
23 | }
24 |
25 | public void onStateChange(Player player, Supplier isGoingInvisible) {
26 | getPlugin().getPluginLogger().debug("Pre Vanish State Change: " + headCache.getHeads().stream().map(i ->
27 | Objects.requireNonNull(i.getItemMeta()).getDisplayName()).toList());
28 | if (isGoingInvisible.get())
29 | headCache.invalidate(player);
30 | else
31 | headCache.getHeadFor(Objects.requireNonNull(player));
32 | getPlugin().getPluginLogger().debug("Post Vanish State Change: " + headCache.getHeads().stream().map(i ->
33 | Objects.requireNonNull(i.getItemMeta()).getDisplayName()).toList());
34 | }
35 |
36 |
37 | @Override
38 | public String toString() {
39 | return this.getClass().getSimpleName();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/hook/hooks/VanishNoPacketHook.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.hook.hooks;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin;
5 | import org.bukkit.Bukkit;
6 | import org.bukkit.entity.Player;
7 | import org.bukkit.event.EventHandler;
8 | import org.bukkit.event.EventPriority;
9 | import org.bukkit.event.Listener;
10 | import org.kitteh.vanish.VanishPerms;
11 | import org.kitteh.vanish.VanishPlugin;
12 | import org.kitteh.vanish.event.VanishStatusChangeEvent;
13 |
14 | @TargetPlugin(pluginName = "VanishNoPacket")
15 | public class VanishNoPacketHook extends VanishHook implements Listener {
16 |
17 | private VanishPlugin vanishPlugin;
18 |
19 | private VanishNoPacketHook(CommandPrompter plugin) {
20 | super(plugin);
21 | var jPlugin = Bukkit.getServer().getPluginManager().getPlugin("VanishNoPacket");
22 | if (jPlugin instanceof VanishPlugin)
23 | this.vanishPlugin = (VanishPlugin) jPlugin;
24 | else
25 | plugin.getPluginLogger().warn("VanishNoPacketHook cannot be initialized without VanishNoPacket");
26 | }
27 |
28 | public boolean isInvisible(Player p) {
29 | if (vanishPlugin == null) return false;
30 | if (p.hasPermission("vanish.hooks.dynmap.alwayshidden") || VanishPerms.joinVanished(p))
31 | return true;
32 | return vanishPlugin.getManager().isVanished(p);
33 | }
34 |
35 | @EventHandler(priority = EventPriority.NORMAL)
36 | public void onVisibilityStateChange(VanishStatusChangeEvent event) {
37 | onStateChange(event.getPlayer(), event::isVanishing);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/listener/CommandListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Ethan Bacurio
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package com.cyr1en.commandprompter.listener;
26 |
27 | import com.cyr1en.commandprompter.CommandPrompter;
28 | import com.cyr1en.commandprompter.prompt.ContextProcessor;
29 | import com.cyr1en.commandprompter.prompt.PromptManager;
30 | import org.bukkit.event.Listener;
31 |
32 | public class CommandListener extends ContextProcessor implements Listener {
33 |
34 | protected CommandPrompter plugin;
35 | protected PromptManager promptManager;
36 |
37 | public CommandListener(PromptManager promptManager) {
38 | super(promptManager.getPlugin(), promptManager);
39 | this.promptManager = promptManager;
40 | this.plugin = promptManager.getPlugin();
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/listener/CommandSendListener.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.listener;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import org.bukkit.event.EventHandler;
5 | import org.bukkit.event.EventPriority;
6 | import org.bukkit.event.Listener;
7 | import org.bukkit.event.player.PlayerCommandSendEvent;
8 |
9 | import java.util.Arrays;
10 |
11 | public class CommandSendListener implements Listener {
12 |
13 | private final CommandPrompter plugin;
14 |
15 | private static final String[] keys = {
16 | "commandprompter:cmdp",
17 | "commandprompter:commandprompter",
18 | "commandprompter:cmdprompter",
19 | "cmdp",
20 | "commandprompter",
21 | "cmdprompter"
22 | };
23 |
24 | public CommandSendListener(CommandPrompter plugin) {
25 | this.plugin = plugin;
26 | }
27 |
28 | @EventHandler(priority = EventPriority.HIGHEST)
29 | private void onCommandSend(PlayerCommandSendEvent event) {
30 | plugin.getPluginLogger().debug("CommandSendEvent caught.");
31 | plugin.getPluginLogger().debug("Complete: " + plugin.getConfiguration().commandTabComplete());
32 | if (!plugin.getConfiguration().commandTabComplete())
33 | event.getCommands().removeAll(Arrays.stream(keys).toList());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/listener/VanillaListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Ethan Bacurio
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package com.cyr1en.commandprompter.listener;
26 |
27 | import com.cyr1en.commandprompter.prompt.PromptContext;
28 | import com.cyr1en.commandprompter.prompt.PromptManager;
29 | import org.bukkit.event.EventHandler;
30 | import org.bukkit.event.EventPriority;
31 | import org.bukkit.event.player.PlayerCommandPreprocessEvent;
32 |
33 | public class VanillaListener extends CommandListener {
34 |
35 | public VanillaListener(PromptManager manager) {
36 | super(manager);
37 | }
38 |
39 | @SuppressWarnings("unused")
40 | @EventHandler(priority = EventPriority.LOWEST)
41 | public void onCommand(PlayerCommandPreprocessEvent event) {
42 | var content = event.getMessage().replaceFirst("/", "");
43 | var context = new PromptContext.Builder()
44 | .setCancellable(event)
45 | .setSender(event.getPlayer())
46 | .setContent(content)
47 | .build();
48 | this.process(context);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/ContextProcessor.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.commands.MainCommand.Cancel;
5 | import org.bukkit.entity.Player;
6 | import org.fusesource.jansi.Ansi;
7 |
8 | import java.util.concurrent.atomic.AtomicBoolean;
9 |
10 | public class ContextProcessor {
11 |
12 | private final CommandPrompter plugin;
13 | private final PromptManager promptManager;
14 |
15 | public ContextProcessor(CommandPrompter plugin, PromptManager promptManager) {
16 | this.plugin = plugin;
17 | this.promptManager = promptManager;
18 | }
19 |
20 | protected void process(PromptContext context) {
21 | // Sanity Checks
22 | plugin.getPluginLogger().debug("Command: " + context.getContent());
23 | plugin.getPluginLogger().debug("Command Caught using: %s", this.getClass().getSimpleName());
24 |
25 | if (isIgnored(context)) {
26 | plugin.getPluginLogger().debug("Caught command is ignored.");
27 | plugin.getPluginLogger().info(new Ansi().fgGreen().a(context.getContent()).reset() + " is configured to be ignored.");
28 | return;
29 | }
30 |
31 | // Check if the command is CommandPrompter's cancel command
32 | if (context.getContent().matches(Cancel.commandPattern.toString()))
33 | return;
34 |
35 | if (!context.getSender().hasPermission("commandprompter.use") &&
36 | plugin.getConfiguration().enablePermission()) {
37 | plugin.getMessenger().sendMessage(context.getSender(),
38 | plugin.getI18N().getProperty("PromptNoPerm"));
39 | return;
40 | }
41 | if (shouldBlock(context)) {
42 | plugin.getMessenger().sendMessage(context.getSender(),
43 | plugin.getI18N().getFormattedProperty("PromptInProgress",
44 | plugin.getConfiguration().cancelKeyword()));
45 | if (context.getCancellable() != null)
46 | context.getCancellable().setCancelled(true);
47 | return;
48 | }
49 |
50 | if (!promptManager.getParser().isParsable(context)) return;
51 | if (!(context.getSender() instanceof Player)) {
52 | plugin.getMessenger().sendMessage(context.getSender(),
53 | plugin.getI18N().getProperty("PromptPlayerOnly"));
54 | return;
55 | }
56 |
57 | if (context.getCancellable() != null)
58 | context.getCancellable().setCancelled(true);
59 |
60 | plugin.getPluginLogger().debug("Ctx Before Parse: " + context);
61 | promptManager.parse(context);
62 | promptManager.sendPrompt(context.getSender());
63 | }
64 |
65 | private boolean shouldBlock(PromptContext context) {
66 | var fulfilling = promptManager.getPromptRegistry().inCommandProcess(context.getSender());
67 | var cmd = extractCommand(context.getContent());
68 | var cmds = plugin.getConfiguration().allowedWhileInPrompt();
69 | return fulfilling && (!cmds.contains(cmd) && promptManager.getParser().isParsable(context));
70 | }
71 |
72 | private boolean isIgnored(PromptContext context) {
73 | var cmd = extractCommand(context.getContent());
74 | return plugin.getConfiguration().ignoredCommands().contains(cmd);
75 | }
76 |
77 | private String extractCommand(String content) {
78 | var end = content.indexOf(" ");
79 | end = end == -1 ? content.length() : end;
80 | return content.substring(0, end);
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/PromptContext.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Ethan Bacurio
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package com.cyr1en.commandprompter.prompt;
26 |
27 | import org.bukkit.command.CommandSender;
28 | import org.bukkit.entity.Player;
29 | import org.bukkit.event.Cancellable;
30 | import org.bukkit.event.player.PlayerCommandPreprocessEvent;
31 |
32 | import javax.annotation.Nullable;
33 | import java.util.Objects;
34 |
35 | /**
36 | * A helper class that holds context for the prompt system.
37 | *
38 | *
39 | * We can think of it as a data container for various functions throughout the
40 | * prompt system.
41 | */
42 | public class PromptContext {
43 | private final Cancellable cancellable;
44 | private final CommandSender sender;
45 | private String content;
46 | private String promptKey;
47 | private boolean isConsoleDelegate;
48 |
49 | private final String paKey;
50 |
51 | public PromptContext(PlayerCommandPreprocessEvent e) {
52 | this(e, e.getPlayer(), e.getMessage(), null, null, false);
53 | }
54 |
55 | public PromptContext(@Nullable Cancellable callable,
56 | Player sender,
57 | String content,
58 | @Nullable String promptKey,
59 | @Nullable String paKey,
60 | boolean isConsoleDelegate) {
61 | this.cancellable = callable;
62 | this.sender = sender;
63 | this.content = content;
64 | this.promptKey = promptKey;
65 | this.paKey = paKey;
66 | this.isConsoleDelegate = isConsoleDelegate;
67 | }
68 |
69 | public CommandSender getSender() {
70 | return sender;
71 | }
72 |
73 | public Cancellable getCancellable() {
74 | return cancellable;
75 | }
76 |
77 | public String getContent() {
78 | return content;
79 | }
80 |
81 | public String getPromptKey() {
82 | return promptKey;
83 | }
84 |
85 | public void setContent(String content) {
86 | this.content = content;
87 | }
88 |
89 | public void setPromptKey(String promptKey) {
90 | this.promptKey = promptKey;
91 | }
92 |
93 | public void setIsConsoleDelegate(boolean b) {
94 | this.isConsoleDelegate = b;
95 | }
96 |
97 | public boolean isConsoleDelegate() {
98 | return this.isConsoleDelegate;
99 | }
100 |
101 | public String getPaKey() {
102 | return Objects.isNull(paKey) ? "" : paKey;
103 | }
104 |
105 | @Override
106 | public String toString() {
107 | return "PromptContext{" +
108 | "cancellable=" + cancellable +
109 | ", sender=" + sender +
110 | ", content='" + content + '\'' +
111 | ", promptKey='" + promptKey + '\'' +
112 | ", paKey='" + paKey + '\'' +
113 | ", isConsoleDelegate=" + isConsoleDelegate +
114 | '}';
115 | }
116 |
117 | // Builder for PromptContext
118 | public static class Builder {
119 | private Cancellable cancellable;
120 | private CommandSender sender;
121 | private String content;
122 |
123 | private String promptKey;
124 |
125 | private String paKey;
126 | private boolean isConsoleDelegate;
127 |
128 | public Builder() {
129 | this.cancellable = null;
130 | this.sender = null;
131 | this.content = null;
132 | this.promptKey = null;
133 | this.paKey = null;
134 | this.isConsoleDelegate = false;
135 | }
136 |
137 | public Builder setCancellable(Cancellable cancellable) {
138 | this.cancellable = cancellable;
139 | return this;
140 | }
141 |
142 | public Builder setSender(CommandSender sender) {
143 | this.sender = sender;
144 | return this;
145 | }
146 |
147 | public Builder setContent(String content) {
148 | this.content = content;
149 | return this;
150 | }
151 |
152 | public Builder setConsoleDelegate(boolean isConsoleDelegate) {
153 | this.isConsoleDelegate = isConsoleDelegate;
154 | return this;
155 | }
156 |
157 | public Builder setPromptKey(String promptKey) {
158 | this.promptKey = promptKey;
159 | return this;
160 | }
161 |
162 | public Builder setPaKey(String paKey) {
163 | this.paKey = paKey;
164 | return this;
165 | }
166 |
167 | public PromptContext build() {
168 | // check if sender and content is null.
169 | if (sender == null || content == null)
170 | throw new IllegalStateException("Sender and content must not be null!");
171 | return new PromptContext(cancellable, (Player) sender,
172 | content, promptKey, paKey, isConsoleDelegate);
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/PromptQueue.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.PluginLogger;
5 | import com.cyr1en.commandprompter.api.Dispatcher;
6 | import com.cyr1en.commandprompter.api.prompt.Prompt;
7 | import com.cyr1en.commandprompter.util.MMUtil;
8 | import com.cyr1en.kiso.utils.SRegex;
9 | import org.bukkit.entity.Player;
10 |
11 | import java.util.Arrays;
12 | import java.util.LinkedList;
13 | import java.util.List;
14 | import java.util.function.Consumer;
15 | import java.util.regex.Pattern;
16 |
17 | public class PromptQueue extends LinkedList {
18 |
19 | private String command;
20 | private final LinkedList completed;
21 | private final String escapedRegex;
22 |
23 | private final boolean isOp;
24 | private final boolean isConsoleDelegate;
25 | private String permissionAttachmentKey;
26 |
27 | private final List postCommandMetas;
28 |
29 | private final PluginLogger logger;
30 |
31 | public PromptQueue(String command, boolean isOp, boolean isDelegate,
32 | String escapedRegex) {
33 | super();
34 | this.command = command;
35 | this.escapedRegex = escapedRegex;
36 | this.completed = new LinkedList<>();
37 | this.isOp = isOp;
38 | this.isConsoleDelegate = isDelegate;
39 | this.permissionAttachmentKey = "";
40 | this.postCommandMetas = new LinkedList<>();
41 | logger = CommandPrompter.getInstance().getPluginLogger();
42 | }
43 |
44 | public void addCompleted(String s) {
45 | completed.add(s);
46 | }
47 |
48 | public boolean isOp() {
49 | return isOp;
50 | }
51 |
52 | public String getPermissionAttachmentKey() {
53 | return permissionAttachmentKey;
54 | }
55 |
56 | public void setPermissionAttachmentKey(String key) {
57 | this.permissionAttachmentKey = key;
58 | }
59 |
60 | public boolean isConsoleDelegate() {
61 | return isConsoleDelegate;
62 | }
63 |
64 | public String getCompleteCommand() {
65 | command = command.formatted(completed);
66 | LinkedList completedClone = new LinkedList<>(this.completed);
67 |
68 | // get all prompts that we have to replace in the command
69 | var sRegex = new SRegex();
70 | var prompts = sRegex.find(Pattern.compile(escapedRegex), command).getResultsList();
71 | prompts = MMUtil.filterOutMiniMessageTags(prompts);
72 |
73 | for (String prompt : prompts) {
74 | if (completedClone.isEmpty())
75 | break;
76 | command = command.replace(prompt, completedClone.pollFirst());
77 | }
78 | return "/" + command;
79 | }
80 |
81 | public void addPCM(PostCommandMeta pcm) {
82 | postCommandMetas.add(pcm);
83 | }
84 |
85 | public boolean containsPCM() {
86 | return postCommandMetas != null && !postCommandMetas.isEmpty();
87 | }
88 |
89 | public List getPostCommandMetas() {
90 | return postCommandMetas;
91 | }
92 |
93 | public String getCommand() {
94 | return command;
95 | }
96 |
97 | public void setCommand(String command) {
98 | this.command = command;
99 | }
100 |
101 | public void dispatch(CommandPrompter plugin, Player sender) {
102 | if (isConsoleDelegate()) {
103 | logger.debug("Dispatching as console");
104 | Dispatcher.dispatchConsole(getCompleteCommand());
105 | } else if (!permissionAttachmentKey.isBlank()) {
106 | Dispatcher.dispatchWithAttachment(plugin, sender, getCompleteCommand(),
107 | plugin.getConfiguration().permissionAttachmentTicks(),
108 | plugin.getConfiguration().getPermissionAttachment(permissionAttachmentKey));
109 | } else
110 | Dispatcher.dispatchCommand(plugin, sender, getCompleteCommand());
111 |
112 | if (!postCommandMetas.isEmpty())
113 | postCommandMetas.forEach(pcm -> {
114 | if (pcm.isOnCancel())
115 | return;
116 |
117 | if (pcm.delayTicks() > 0)
118 | plugin.getServer().getScheduler().runTaskLater(plugin, () -> execPCM(pcm, sender),
119 | pcm.delayTicks());
120 | else
121 | execPCM(pcm, sender);
122 | });
123 | }
124 |
125 | void execPCM(PostCommandMeta postCommandMeta, Player sender) {
126 | logger.debug("Executing PCM: " + postCommandMeta.command());
127 |
128 | var completedClone = new LinkedList<>(completed);
129 | var i18N = CommandPrompter.getInstance().getI18N();
130 | var command = postCommandMeta.makeAsCommand(completedClone, index -> {
131 | var message = i18N.getFormattedProperty("PCMOutOfBounds", index);
132 | CommandPrompter.getInstance().getMessenger().sendMessage(sender, message);
133 | });
134 | logger.debug("After parse: " + command);
135 |
136 | if (isConsoleDelegate()) {
137 | logger.debug("Dispatching PostCommand as console");
138 | Dispatcher.dispatchConsole(command);
139 | } else {
140 | logger.debug("Dispatching PostCommand as player");
141 | Dispatcher.dispatchCommand(CommandPrompter.getInstance(), sender, command);
142 | }
143 |
144 | }
145 |
146 | /**
147 | * @param promptIndex This will hold the index of the prompt answers to be
148 | * injected in this post command.
149 | */
150 | public record PostCommandMeta(String command, int[] promptIndex, int delayTicks, boolean isOnCancel) {
151 | @Override
152 | public String toString() {
153 | return "PostCommandMeta{" + "command='" + command + '\'' + ", promptIndex=" + Arrays.toString(promptIndex)
154 | + ", delayTicks=" + delayTicks + ", isOnCancel=" + isOnCancel + '}';
155 | }
156 |
157 | public String makeAsCommand(LinkedList promptAnswers) {
158 | return makeAsCommand(promptAnswers, index -> {
159 | });
160 | }
161 |
162 | public String makeAsCommand(LinkedList promptAnswers, Consumer onOutOfBounds) {
163 | if (promptAnswers == null || promptAnswers.isEmpty())
164 | return command;
165 | var command = this.command;
166 | var promptIndex = this.promptIndex;
167 | for (int index : promptIndex) {
168 | if (index >= promptAnswers.size() || index < 0) {
169 | onOutOfBounds.accept(String.valueOf(index));
170 | continue;
171 | }
172 | command = command.replaceFirst("p:" + index, promptAnswers.get(index));
173 | }
174 | return command;
175 | }
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/PromptRegistry.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Ethan Bacurio
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package com.cyr1en.commandprompter.prompt;
25 |
26 | import com.cyr1en.commandprompter.CommandPrompter;
27 | import com.cyr1en.commandprompter.api.prompt.Prompt;
28 | import org.bukkit.command.CommandSender;
29 |
30 | import java.util.HashMap;
31 | import java.util.Objects;
32 |
33 | /**
34 | * Class that will hold all ongoing prompts.
35 | *
36 | * The data structure of this class is a hash table. The key of this data struct
37 | * is the command
38 | * sender itself and the value is a {@link PromptQueue}
39 | */
40 | public class PromptRegistry extends HashMap {
41 |
42 | private final CommandPrompter pluginInstance;
43 |
44 | public PromptRegistry(CommandPrompter pluginInstance) {
45 | this.pluginInstance = pluginInstance;
46 | }
47 |
48 | public void initRegistryFor(PromptContext context, String command, String escapedRegex) {
49 | if (containsKey(context.getSender()))
50 | return;
51 |
52 | var queue = new PromptQueue(
53 | command,
54 | context.getSender().isOp(),
55 | context.isConsoleDelegate(),
56 | escapedRegex);
57 | queue.setPermissionAttachmentKey(context.getPaKey());
58 |
59 | put(context.getSender(), queue);
60 | }
61 |
62 | public void addPrompt(CommandSender sender, Prompt p) {
63 | if (!containsKey(sender))
64 | return;
65 | get(sender).add(p);
66 | pluginInstance.getPluginLogger().debug("Registered: (%s : %s)",
67 | sender.getName(), p.getClass().getSimpleName());
68 | }
69 |
70 | public void unregister(CommandSender sender) {
71 | if (!containsKey(sender))
72 | return;
73 | remove(sender);
74 | pluginInstance.getPluginLogger().debug("Un-Registered: %s", sender.getName());
75 | }
76 |
77 | public boolean inCommandProcess(CommandSender sender) {
78 | if (!containsKey(sender))
79 | return false;
80 | if (Objects.isNull(get(sender))) {
81 | remove(sender);
82 | return false;
83 | }
84 | return true;
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/prompts/AbstractPrompt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Ethan Bacurio
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package com.cyr1en.commandprompter.prompt.prompts;
26 |
27 | import com.cyr1en.commandprompter.CommandPrompter;
28 | import com.cyr1en.commandprompter.api.prompt.InputValidator;
29 | import com.cyr1en.commandprompter.api.prompt.Prompt;
30 | import com.cyr1en.commandprompter.prompt.PromptContext;
31 | import com.cyr1en.commandprompter.prompt.PromptManager;
32 | import com.cyr1en.commandprompter.prompt.PromptParser;
33 | import com.cyr1en.commandprompter.prompt.validators.NoopValidator;
34 | import com.cyr1en.commandprompter.util.Util;
35 |
36 | import java.util.List;
37 |
38 | public abstract class AbstractPrompt implements Prompt {
39 |
40 | private final CommandPrompter plugin;
41 | private final PromptContext context;
42 | private final String prompt;
43 | private final PromptManager promptManager;
44 |
45 | private final List args;
46 |
47 | private InputValidator validator;
48 |
49 | private boolean inputSanitation;
50 |
51 | public AbstractPrompt(CommandPrompter plugin, PromptContext context,
52 | String prompt, List args) {
53 | this.plugin = plugin;
54 | this.context = context;
55 | this.prompt = prompt;
56 | this.promptManager = plugin.getPromptManager();
57 | this.args = args;
58 | this.inputSanitation = true;
59 | this.validator = new NoopValidator();
60 | }
61 |
62 | protected String stripColor(String msg) {
63 | return Util.stripColor(msg);
64 | }
65 |
66 | protected String color(String msg) {
67 | return Util.color(msg);
68 | }
69 |
70 | @Override
71 | public abstract void sendPrompt();
72 |
73 | @Override
74 | public PromptContext getContext() {
75 | return context;
76 | }
77 |
78 | @Override
79 | public CommandPrompter getPlugin() {
80 | return plugin;
81 | }
82 |
83 | @Override
84 | public String getPrompt() {
85 | return prompt;
86 | }
87 |
88 | @Override
89 | public PromptManager getPromptManager() {
90 | return promptManager;
91 | }
92 |
93 | @Override
94 | public List getArgs() {
95 | return args;
96 | }
97 |
98 | @Override
99 | public void setInputValidator(InputValidator inputValidator) {
100 | this.validator = inputValidator;
101 | }
102 |
103 | @Override
104 | public InputValidator getInputValidator() {
105 | return this.validator;
106 | }
107 |
108 | @Override
109 | public void setInputSanitization(boolean sanitize) {
110 | this.inputSanitation = sanitize;
111 | }
112 |
113 | @Override
114 | public boolean sanitizeInput() {
115 | return this.inputSanitation;
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/prompts/AnvilPrompt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Ethan Bacurio
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package com.cyr1en.commandprompter.prompt.prompts;
26 |
27 | import com.cyr1en.commandprompter.CommandPrompter;
28 | import com.cyr1en.commandprompter.prompt.PromptContext;
29 | import com.cyr1en.commandprompter.prompt.PromptParser;
30 | import com.cyr1en.commandprompter.util.ServerUtil;
31 | import com.cyr1en.commandprompter.util.Util;
32 | import net.wesjd.anvilgui.AnvilGUI;
33 | import org.bukkit.ChatColor;
34 | import org.bukkit.Material;
35 | import org.bukkit.enchantments.Enchantment;
36 | import org.bukkit.entity.Player;
37 | import org.bukkit.inventory.ItemFlag;
38 | import org.bukkit.inventory.ItemStack;
39 | import org.jetbrains.annotations.NotNull;
40 |
41 | import java.util.Arrays;
42 | import java.util.Collections;
43 | import java.util.List;
44 | import java.util.Objects;
45 | import java.util.concurrent.atomic.AtomicBoolean;
46 |
47 | public class AnvilPrompt extends AbstractPrompt {
48 |
49 | public static char BLANK_CHAR = '\u00A0'; // No-Break Space
50 |
51 | public AnvilPrompt(CommandPrompter plugin, PromptContext context,
52 | String prompt, List args) {
53 | super(plugin, context, prompt, args);
54 | }
55 |
56 | @Override
57 | public void sendPrompt() {
58 | List parts = Arrays.asList(getPrompt().split("\\{br}"));
59 | var item = makeAnvilItem(parts);
60 | var resultItem = makeResultItem(parts);
61 | var cancelItem = makeCancelItem(parts);
62 | makeAnvil(parts, item, resultItem, cancelItem).open((Player) getContext().getSender());
63 | }
64 |
65 | private AnvilGUI.Builder makeAnvil(List parts, ItemStack item, ItemStack resultItem, ItemStack cancelItem) {
66 | var isComplete = new AtomicBoolean(false);
67 | var builder = getBuilder(isComplete);
68 | builder.onClose(p -> {
69 | if (isComplete.get())
70 | return;
71 | getPromptManager().cancel(p.getPlayer());
72 | });
73 |
74 | var promptText = getPlugin().getPromptConfig().promptMessage();
75 |
76 | var text = (promptText.isBlank()) ? color(parts.get(0)) : promptText.equals("BLANK") ?
77 | String.valueOf(BLANK_CHAR) : color(promptText);
78 | builder.text(text);
79 |
80 | if (getPlugin().getPromptConfig().enableTitle()) {
81 | var title = getPlugin().getPromptConfig().customTitle();
82 | title = title.isEmpty() ? color(parts.get(0)) : color(title);
83 | builder.title(title);
84 | }
85 |
86 | if (getPlugin().getPromptConfig().enableCancelItem())
87 | builder.itemRight(cancelItem);
88 |
89 | builder.itemLeft(item);
90 | builder.itemOutput(resultItem);
91 | builder.plugin(getPlugin());
92 | return builder;
93 | }
94 |
95 | @NotNull
96 | private AnvilGUI.Builder getBuilder(AtomicBoolean isComplete) {
97 | var builder = new AnvilGUI.Builder();
98 | builder.onClick((slot, stateSnapshot) -> {
99 | var cancelEnabled = getPlugin().getPromptConfig().enableCancelItem();
100 | if (slot == AnvilGUI.Slot.INPUT_RIGHT && cancelEnabled) {
101 | getPromptManager().cancel(stateSnapshot.getPlayer());
102 | return Collections.singletonList(AnvilGUI.ResponseAction.close());
103 | }
104 |
105 | if (slot != AnvilGUI.Slot.OUTPUT)
106 | return Collections.emptyList();
107 |
108 | var message = ChatColor.stripColor(
109 | ChatColor.translateAlternateColorCodes('&', stateSnapshot.getText()));
110 | var cancelKeyword = getPlugin().getConfiguration().cancelKeyword();
111 | if (cancelKeyword.equalsIgnoreCase(message)) {
112 | getPromptManager().cancel(stateSnapshot.getPlayer());
113 | return Collections.singletonList(AnvilGUI.ResponseAction.close());
114 | }
115 |
116 | isComplete.getAndSet(true);
117 | var content = stateSnapshot.getText().replaceAll(String.valueOf(BLANK_CHAR), "");
118 | var ctx = new PromptContext.Builder()
119 | .setSender(stateSnapshot.getPlayer())
120 | .setContent(content).build();
121 |
122 | getPromptManager().processPrompt(ctx);
123 | return Collections.singletonList(AnvilGUI.ResponseAction.close());
124 | });
125 | return builder;
126 | }
127 |
128 | private ItemStack makeItem(String prefix, List parts, String customTitle) {
129 | var config = getPlugin().getPromptConfig().rawConfig();
130 | var material = config.getString(prefix + ".Material", "PAPER");
131 | var enchanted = config.getBoolean(prefix + ".Enchanted", false);
132 | var customModelData = config.getInt(prefix + ".Custom-Model-Data", 0);
133 | var hideTooltips = config.getBoolean(prefix + ".HideTooltips", false);
134 |
135 | var item = new ItemStack(Util.getCheckedMaterial(material, Material.PAPER));
136 | var meta = item.getItemMeta();
137 | getPlugin().getPluginLogger().debug("ItemMeta: " + meta);
138 |
139 | if (enchanted) {
140 | Objects.requireNonNull(meta).addEnchant(Enchantment.LURE, 1, true);
141 | meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
142 | }
143 |
144 | Objects.requireNonNull(meta).addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
145 | if (customTitle != null)
146 | meta.setDisplayName(customTitle);
147 | else {
148 | meta.setDisplayName(parts.get(0));
149 |
150 | meta.setDisplayName(parts.get(0));
151 |
152 | if (parts.size() > 1)
153 | meta.setLore(parts.subList(1, parts.size()).stream().map(this::color).toList());
154 |
155 | if (customModelData != 0)
156 | meta.setCustomModelData(customModelData);
157 |
158 | if (ServerUtil.isAtOrAbove("1.21.2")) {
159 | meta.setHideTooltip(hideTooltips);
160 | }
161 | }
162 |
163 | item.setItemMeta(meta);
164 | return item;
165 | }
166 |
167 | private ItemStack makeItem(String prefix, List parts) {
168 | return makeItem(prefix, parts, null);
169 | }
170 |
171 | private ItemStack makeAnvilItem(List parts) {
172 | return makeItem("AnvilGUI.Item", parts);
173 | }
174 |
175 | private ItemStack makeResultItem(List parts) {
176 | return makeItem("AnvilGUI.ResultItem", parts);
177 | }
178 |
179 | private ItemStack makeCancelItem(List parts) {
180 | var cancelText = getPlugin().getPromptConfig().cancelItemHoverText();
181 | return makeItem("AnvilGUI.CancelItem", parts, Util.color(cancelText));
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/prompts/SignPrompt.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt.prompts;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.prompt.PromptContext;
5 | import com.cyr1en.commandprompter.prompt.PromptParser;
6 | import com.cyr1en.commandprompter.util.Util;
7 | import com.cyr1en.kiso.utils.FastStrings;
8 | import de.rapha149.signgui.*;
9 | import de.rapha149.signgui.exception.SignGUIVersionException;
10 | import org.bukkit.Material;
11 | import org.bukkit.entity.Player;
12 |
13 | import java.util.Arrays;
14 | import java.util.Collections;
15 | import java.util.List;
16 |
17 | public class SignPrompt extends AbstractPrompt {
18 |
19 | private static final String MULTI_ARG_PATTERN_EMPTY = "[\\S+]+:";
20 | private static final String MULTI_ARG_PATTERN_FILLED = MULTI_ARG_PATTERN_EMPTY + "\\s?+[\\S+]+";
21 |
22 | private boolean isMultiArg;
23 |
24 | public SignPrompt(CommandPrompter plugin, PromptContext context, String prompt,
25 | List args) {
26 | super(plugin, context, prompt, args);
27 | isMultiArg = false;
28 | }
29 |
30 | @Override
31 | public void sendPrompt() {
32 | List parts = Arrays.asList(getPrompt().split("\\{br}"));
33 | checkMultiArg(parts);
34 | getPlugin().getPluginLogger().debug("Is Multi-Arg: " + isMultiArg);
35 | if (parts.size() > 3 && !isMultiArg)
36 | parts = parts.subList(0, 3);
37 | else if (parts.size() > 4)
38 | parts = parts.subList(0, 4);
39 |
40 | List finalParts = parts;
41 |
42 | var matStr = getPlugin().getPromptConfig().signMaterial();
43 | var mat = Util.getCheckedMaterial(matStr, Material.OAK_SIGN);
44 | getPlugin().getPluginLogger().debug("Material: " + mat.name());
45 |
46 | try {
47 | var gui = SignGUI.builder()
48 | .setLines(finalParts.toArray(String[]::new))
49 | .setType(mat)
50 | .setHandler((p, r) -> process(finalParts, p, r.getLines()))
51 | .build();
52 |
53 | gui.open((Player) getContext().getSender());
54 | } catch (SignGUIVersionException e) {
55 | getPlugin().getPluginLogger().err("SignGUI version exception: " + e.getMessage());
56 | getPromptManager().cancel(getContext().getSender());
57 | }
58 | }
59 |
60 | private void checkMultiArg(List parts) {
61 | isMultiArg = parts.stream().map(String::trim).anyMatch(s -> s.matches(MULTI_ARG_PATTERN_EMPTY));
62 | }
63 |
64 | private List process(List parts, Player p, String[] s) {
65 | var cleanedParts = parts.stream().map(this::stripColor).toList();
66 | getPlugin().getPluginLogger().debug("Sign Strings: " + Arrays.toString(s));
67 |
68 | var response = isMultiArg
69 | ? FastStrings.join(Arrays.stream(s).filter(str -> !str.isBlank() && !cleanedParts.contains(str))
70 | .filter(str -> str.matches(MULTI_ARG_PATTERN_FILLED))
71 | .map(str -> str.replaceAll(MULTI_ARG_PATTERN_EMPTY, "").trim()).toArray(), " ")
72 | : FastStrings.join(Arrays.stream(s)
73 | .filter(str -> !cleanedParts.contains(str) && !str.isBlank()).toArray(), " ");
74 |
75 | getPlugin().getPluginLogger().debug("Response: " + response);
76 |
77 | // If the sign contains the same message as the prompt
78 | // we'll consider the command completion cancelled.
79 | if (response.isBlank()) {
80 | getPromptManager().cancel(p);
81 | return Collections.emptyList();
82 | }
83 |
84 | var cancelKeyword = getPlugin().getConfiguration().cancelKeyword();
85 | if (cancelKeyword.equalsIgnoreCase(response)) {
86 | getPromptManager().cancel(p);
87 | return Collections.emptyList();
88 | }
89 | var ctx = new PromptContext.Builder()
90 | .setSender(p)
91 | .setContent(response).build();
92 |
93 | getPromptManager().processPrompt(ctx);
94 |
95 | return Collections.emptyList();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt.ui;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.PluginLogger;
5 | import com.cyr1en.commandprompter.config.PromptConfig;
6 | import com.cyr1en.commandprompter.prompt.PromptParser;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.OfflinePlayer;
9 | import org.bukkit.entity.Player;
10 |
11 | import java.util.List;
12 | import java.util.Objects;
13 | import java.util.regex.Pattern;
14 |
15 | /**
16 | * Class that would filter players based on a regex key.
17 | *
18 | * For example, if the regex key is 'w', then it would filter all players
19 | * that are in the same world as the relative player.
20 | */
21 | public abstract class CacheFilter {
22 |
23 |
24 | /**
25 | * The regex key that would be used for parsing
26 | */
27 | private final Pattern regexKey;
28 |
29 | /**
30 | * The config key for this cache filter's format
31 | */
32 | private final String configKey;
33 |
34 | private final int capGroupOffset;
35 |
36 | /**
37 | * Construct a new cache filter.
38 | *
39 | * @param regexKey the regex key
40 | */
41 | public CacheFilter(Pattern regexKey, String configKey) {
42 | this(regexKey, configKey, 0);
43 | }
44 |
45 | public CacheFilter(Pattern regexKey, String configKey, int capGroupOffset) {
46 | this.regexKey = regexKey;
47 | this.configKey = configKey;
48 | this.capGroupOffset = capGroupOffset;
49 | }
50 |
51 | /**
52 | * Get the regex key.
53 | *
54 | * @return the regex key
55 | */
56 | public Pattern getRegexKey() {
57 | return regexKey;
58 | }
59 |
60 | /**
61 | * Get the config key.
62 | *
63 | * @return the config key
64 | */
65 | public String getConfigKey() {
66 | return configKey;
67 | }
68 |
69 | /**
70 | * Get the format for this cache filter.
71 | *
72 | * @param config the prompt config
73 | * @return the format
74 | */
75 | public String getFormat(PromptConfig config) {
76 | return config.getFilterFormat(this);
77 |
78 | }
79 |
80 | /**
81 | * Get the capture group offset.
82 | *
83 | * @return the capture group offset
84 | */
85 | public int getCapGroupOffset() {
86 | return capGroupOffset;
87 | }
88 |
89 | @Override
90 | public String toString() {
91 | return this.getClass().getSimpleName();
92 | }
93 |
94 | /**
95 | * A method that allows you to reconstruct a subclass of {@link CacheFilter}
96 | * based on the prompt key.
97 | *
98 | *
99 | * In cases where the prompt key contains additional information, this method
100 | * could be used to reconstruct a certain subclass of {@link CacheFilter}.
101 | *
102 | *
103 | * Additional filter information can be parsed from the {@link PromptParser}
104 | * but for better readability, it is recommended to use this method instead.
105 | *
106 | * @param promptKey the prompt key
107 | * @return the cloned cache filter
108 | */
109 | public abstract CacheFilter reConstruct(String promptKey);
110 |
111 | /**
112 | * Abstract method that would filter player based on subclass implementation.
113 | *
114 | *
115 | * This method would be called by {@link HeadCache} when it needs to filter
116 | * players.
117 | *
118 | * @param relativePlayer the players to filter
119 | * @return the filtered players
120 | */
121 | public abstract List filter(Player relativePlayer);
122 |
123 |
124 | /**
125 | * Filter players based on the world of the relative player.
126 | *
127 | * This is a special case of {@link CacheFilter} where the regex key is 'w'.
128 | * This would filter all players that are in the same world as the relative player.
129 | */
130 | public static class WorldFilter extends CacheFilter {
131 |
132 | /**
133 | * Construct a new world filter.
134 | */
135 | public WorldFilter() {
136 | super(Pattern.compile("w"), "PlayerUI.Filter-Format.World");
137 | }
138 |
139 | @Override
140 | public List filter(Player relativePlayer) {
141 | return relativePlayer.getWorld().getPlayers();
142 | }
143 |
144 | public CacheFilter reConstruct(String promptKey) {
145 | return new WorldFilter();
146 | }
147 | }
148 |
149 | /**
150 | * Filter players based on the radius of the relative player.
151 | *
152 | * This is a special case of {@link CacheFilter} where the regex key is 'r'.
153 | * This would filter all players that are within the radius of the relative player.
154 | */
155 | public static class RadialFilter extends CacheFilter {
156 |
157 | /**
158 | * The radius to check relative to the relative player
159 | */
160 | private final int radius;
161 |
162 | /**
163 | * Construct a new radius filter.
164 | *
165 | * @param radius the radius
166 | */
167 | public RadialFilter(int radius) {
168 | super(Pattern.compile("r(\\d+)"), "PlayerUI.Filter-Format.Radial", 1);
169 | this.radius = radius;
170 | }
171 |
172 | /**
173 | * Construct a new radius filter with a radius of 0.
174 | */
175 | public RadialFilter() {
176 | this(0);
177 | }
178 |
179 | public CacheFilter reConstruct(String promptKey) {
180 | var matcher = this.getRegexKey().matcher(promptKey);
181 | var found = matcher.find();
182 | //print all groups
183 | for (int i = 0; i <= matcher.groupCount(); i++) {
184 | CommandPrompter.getInstance().getPluginLogger().debug("Group %d: %s", i, matcher.group(i));
185 | }
186 | var radius = found ? Integer.parseInt(matcher.group(1)) : 0;
187 | return new RadialFilter(radius);
188 | }
189 |
190 | @Override
191 | public List filter(Player relativePlayer) {
192 | return relativePlayer.getWorld().getPlayers().stream()
193 | .filter(p -> p.getLocation().distance(relativePlayer.getLocation()) <= radius)
194 | .toList();
195 | }
196 | }
197 |
198 | public static class SelfFilter extends CacheFilter {
199 |
200 | public SelfFilter() {
201 | super(Pattern.compile("s"), "PlayerUI.Filter-Format.Self");
202 | }
203 |
204 | @Override
205 | public List filter(Player relativePlayer) {
206 | return Bukkit.getOnlinePlayers().stream()
207 | .map(OfflinePlayer::getPlayer)
208 | .filter(Objects::nonNull)
209 | .filter(p -> !p.equals(relativePlayer))
210 | .toList();
211 | }
212 |
213 | public CacheFilter reConstruct(String promptKey) {
214 | return new SelfFilter();
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/ui/inventory/ControlPane.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt.ui.inventory;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.prompt.PromptContext;
5 | import com.cyr1en.commandprompter.prompt.prompts.PlayerUIPrompt;
6 | import com.cyr1en.commandprompter.util.ServerUtil;
7 | import com.cyr1en.commandprompter.util.Util;
8 | import com.github.stefvanschie.inventoryframework.gui.GuiItem;
9 | import com.github.stefvanschie.inventoryframework.gui.type.ChestGui;
10 | import com.github.stefvanschie.inventoryframework.pane.PaginatedPane;
11 | import com.github.stefvanschie.inventoryframework.pane.StaticPane;
12 | import net.wesjd.anvilgui.AnvilGUI;
13 | import org.bukkit.Material;
14 | import org.bukkit.entity.Player;
15 | import org.bukkit.event.inventory.InventoryClickEvent;
16 | import org.bukkit.inventory.ItemStack;
17 |
18 | import java.util.Collections;
19 | import java.util.Objects;
20 | import java.util.function.Consumer;
21 |
22 | public class ControlPane extends StaticPane {
23 | private static final int DEFAULT_PREV_LOC = 2;
24 | private static final int DEFAULT_NEXT_LOC = 6;
25 | private static final int DEFAULT_CANCEL_LOC = 4;
26 | private static final int DEFAULT_SEARCH_LOC = 8;
27 |
28 | private final CommandPrompter plugin;
29 | private final PaginatedPane paginatedPane;
30 | private final ChestGui gui;
31 | private final PromptContext ctx;
32 | private final PlayerUIPrompt playerUIPrompt;
33 |
34 | private int prevLoc;
35 | private int nextLoc;
36 | private int cancelLoc;
37 | private int searchLoc;
38 |
39 | public ControlPane(CommandPrompter plugin, PaginatedPane pane, ChestGui gui, PromptContext ctx, int numCols, PlayerUIPrompt playerUIPrompt) {
40 | super(0, numCols - 1, 9, 1);
41 | this.plugin = plugin;
42 | this.playerUIPrompt = playerUIPrompt;
43 | prevLoc = plugin.getPromptConfig().previousColumn() - 1;
44 | nextLoc = plugin.getPromptConfig().nextColumn() - 1;
45 | cancelLoc = plugin.getPromptConfig().cancelColumn() - 1;
46 | searchLoc = plugin.getPromptConfig().searchColumn() - 1;
47 |
48 | this.paginatedPane = pane;
49 | this.ctx = ctx;
50 | this.gui = gui;
51 | verifyLocs();
52 | setupButtons();
53 | }
54 |
55 | private void verifyLocs() {
56 | if (prevLoc == nextLoc || prevLoc == cancelLoc || nextLoc == cancelLoc) {
57 | this.prevLoc = DEFAULT_PREV_LOC;
58 | this.nextLoc = DEFAULT_NEXT_LOC;
59 | this.cancelLoc = DEFAULT_CANCEL_LOC;
60 | this.searchLoc = DEFAULT_SEARCH_LOC;
61 | }
62 | }
63 |
64 | private void updatePage(InventoryClickEvent event, int nextPage) {
65 | event.setCancelled(true);
66 | try {
67 | paginatedPane.setPage(nextPage);
68 | gui.update();
69 | } catch (IndexOutOfBoundsException ignore) {
70 | plugin.getPluginLogger().debug("Could not update page.");
71 | }
72 | }
73 |
74 | private void setupButtons() {
75 | var pages = paginatedPane.getPages() - 1;
76 |
77 | var prevMatString = plugin.getPromptConfig().previousItem();
78 | var prevCMD = plugin.getPromptConfig().previousCustomModelData();
79 | var prevIS = new ItemStack(Util.getCheckedMaterial(prevMatString, Material.FEATHER));
80 | addItem(plugin.getPromptConfig().previousText(), prevIS, prevLoc, prevCMD,
81 | c -> updatePage(c, Math.max((paginatedPane.getPage() - 1), 0)));
82 |
83 | var nextMatString = plugin.getPromptConfig().nextItem();
84 | var nextCMD = plugin.getPromptConfig().nextCustomModelData();
85 | var nextIS = new ItemStack(Util.getCheckedMaterial(nextMatString, Material.FEATHER));
86 | addItem(plugin.getPromptConfig().nextText(), nextIS, nextLoc, nextCMD,
87 | c -> updatePage(c, Math.min((paginatedPane.getPage() + 1), pages)));
88 |
89 | var cancelMatString = plugin.getPromptConfig().cancelItem();
90 | var cancelCMD = plugin.getPromptConfig().cancelCustomModelData();
91 | var cancelIS = new ItemStack(Util.getCheckedMaterial(cancelMatString, Material.FEATHER));
92 | addItem(plugin.getPromptConfig().cancelText(), cancelIS, cancelLoc, cancelCMD,
93 | c -> {
94 | c.setCancelled(true);
95 | plugin.getPromptManager().cancel(ctx.getSender());
96 | ((Player) ctx.getSender()).closeInventory();
97 | });
98 |
99 | var searchMatString = plugin.getPromptConfig().searchItem();
100 | var searchCMD = plugin.getPromptConfig().searchCustomModelData();
101 | var searchIS = new ItemStack(Util.getCheckedMaterial(searchMatString, Material.NAME_TAG));
102 | addItem(plugin.getPromptConfig().searchText(), searchIS, searchLoc, searchCMD, this::search);
103 | }
104 |
105 | private void search(InventoryClickEvent e) {
106 | playerUIPrompt.setSearching(true);
107 | var builder = new AnvilGUI.Builder();
108 | builder.onClick((slot, stateSnapshot) -> {
109 | if (slot != AnvilGUI.Slot.OUTPUT) {
110 | return Collections.emptyList();
111 | }
112 |
113 | var paginatedPane = gui.getPanes().stream().filter(pane -> pane instanceof PaginatedPane)
114 | .map(pane -> (PaginatedPane) pane).findFirst().orElse(null);
115 | if (paginatedPane == null) {
116 | plugin.getPluginLogger().debug("PaginatedPane not found.");
117 | return Collections.singletonList(AnvilGUI.ResponseAction.close());
118 | }
119 |
120 | var input = Util.stripColor(stateSnapshot.getText());
121 | plugin.getPluginLogger().debug("Search: " + input);
122 |
123 | var heads = paginatedPane.getItems().stream().map(GuiItem::getItem).filter(item -> {
124 | var meta = item.getItemMeta();
125 | if (meta == null) return false;
126 | var displayName = Util.stripColor(meta.getDisplayName());
127 | return displayName.toLowerCase().contains(input.toLowerCase());
128 | }).toList();
129 | paginatedPane.clear();
130 | paginatedPane.populateWithItemStacks(heads);
131 | gui.show(e.getWhoClicked());
132 | return Collections.singletonList(AnvilGUI.ResponseAction.close());
133 | });
134 |
135 | builder.title(Util.color(plugin.getPromptConfig().searchAnvilItemTitle()));
136 | builder.plugin(plugin);
137 | builder.itemLeft(getSearchLeftItem());
138 | builder.onClose(c -> playerUIPrompt.setSearching(false));
139 | builder.open((Player) e.getWhoClicked());
140 | }
141 |
142 | private ItemStack getSearchLeftItem() {
143 | var item = new ItemStack(Material.PAPER);
144 | var itemMeta = item.getItemMeta();
145 | if (itemMeta == null) {
146 | plugin.getPluginLogger().debug("ItemMeta is null.");
147 | return item;
148 | }
149 | var configText = plugin.getPromptConfig().searchAnvilItemText();
150 | itemMeta.setDisplayName(Util.color(configText));
151 |
152 | itemMeta.setCustomModelData(plugin.getPromptConfig().searchAnvilItemCustomModelData());
153 |
154 | if (ServerUtil.isAtOrAbove("1.21.2"))
155 | itemMeta.setHideTooltip(true);
156 |
157 | item.setItemMeta(itemMeta);
158 | return item;
159 | }
160 |
161 | private void addItem(String name, ItemStack itemStack, int x, int customModelData,
162 | Consumer consumer) {
163 | var itemMeta = itemStack.getItemMeta();
164 | Objects.requireNonNull(itemMeta).setDisplayName(Util.color(name));
165 | itemMeta.setCustomModelData(customModelData == 0 ? null : customModelData);
166 | itemStack.setItemMeta(itemMeta);
167 | addItem(new GuiItem(itemStack, consumer), x, 0);
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/validators/CompoundedValidator.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt.validators;
2 |
3 | import com.cyr1en.commandprompter.api.prompt.CompoundableValidator;
4 | import com.cyr1en.commandprompter.api.prompt.InputValidator;
5 | import org.bukkit.entity.Player;
6 |
7 | import java.util.stream.Stream;
8 |
9 | public class CompoundedValidator implements InputValidator {
10 |
11 | private final String alias;
12 | private final String messageOnFail;
13 | private final Player inputPlayer;
14 | private final InputValidator[] validators;
15 |
16 |
17 | public CompoundedValidator(String alias, String messageOnFail, Player inputPlayer, InputValidator... validators) {
18 | this.alias = alias;
19 | this.messageOnFail = messageOnFail;
20 | this.inputPlayer = inputPlayer;
21 | this.validators = validators;
22 | }
23 |
24 | @Override
25 | public boolean validate(String input) {
26 | if (input == null || input.isBlank())
27 | return false;
28 |
29 | var andValidators = getValidatorsWithType(CompoundableValidator.Type.AND);
30 | var orValidators = getValidatorsWithType(CompoundableValidator.Type.OR);
31 |
32 | return andValidators.allMatch(validator -> validator.validate(input))
33 | || orValidators.anyMatch(validator -> validator.validate(input));
34 | }
35 |
36 | private Stream getValidatorsWithType(CompoundableValidator.Type type) {
37 | return Stream.of(validators)
38 | .filter(validator -> validator instanceof CompoundableValidator)
39 | .filter(validator -> ((CompoundableValidator) validator).getType() == type);
40 | }
41 |
42 | @Override
43 | public String alias() {
44 | return this.alias;
45 | }
46 |
47 | @Override
48 | public String messageOnFail() {
49 | return this.messageOnFail;
50 | }
51 |
52 | @Override
53 | public Player inputPlayer() {
54 | return this.inputPlayer;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/validators/JSExprValidator.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt.validators;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.PluginLogger;
5 | import com.cyr1en.commandprompter.api.prompt.CompoundableValidator;
6 | import com.cyr1en.commandprompter.api.prompt.InputValidator;
7 | import com.cyr1en.commandprompter.hook.hooks.PapiHook;
8 | import org.bukkit.Bukkit;
9 | import org.bukkit.entity.Player;
10 | import org.bukkit.plugin.RegisteredServiceProvider;
11 | import org.bukkit.plugin.ServicePriority;
12 | import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
13 |
14 | import javax.script.ScriptEngineManager;
15 |
16 | public class JSExprValidator implements InputValidator, CompoundableValidator {
17 |
18 | public static final CompoundableValidator.Type DEFAULT_TYPE = Type.AND;
19 | private final String alias;
20 | private final String expression;
21 | private final String messageOnFail;
22 | private final Player inputPlayer;
23 | private final PluginLogger logger;
24 | private ScriptEngineManager engine;
25 |
26 | private CompoundableValidator.Type type = DEFAULT_TYPE;
27 |
28 | public JSExprValidator(String alias, String expression, String messageOnFail, Player inputPlayer) {
29 | this.alias = alias;
30 | this.expression = expression;
31 | this.messageOnFail = messageOnFail;
32 | this.inputPlayer = inputPlayer;
33 | this.logger = CommandPrompter.getInstance().getPluginLogger();
34 | initEngine();
35 | }
36 |
37 | private void initEngine() {
38 | var manager = Bukkit.getServer().getServicesManager();
39 | var factory = new NashornScriptEngineFactory();
40 | if (engine == null) {
41 | if (manager.isProvidedFor(ScriptEngineManager.class)) {
42 | final RegisteredServiceProvider provider = manager.getRegistration(ScriptEngineManager.class);
43 | if (provider == null) {
44 | logger.debug("ScriptEngineManager provider is null");
45 | return;
46 | }
47 | engine = provider.getProvider();
48 | } else {
49 | engine = new ScriptEngineManager();
50 | manager.register(ScriptEngineManager.class, engine, CommandPrompter.getInstance(), ServicePriority.Highest);
51 | }
52 | engine.registerEngineName("JavaScript", factory);
53 | engine.put("BukkitServer", Bukkit.getServer());
54 | }
55 | }
56 |
57 | @Override
58 | public boolean validate(String input) {
59 | var exprStr = expression.replace("%prompt_input%", input);
60 |
61 | var hook = CommandPrompter.getInstance().getHookContainer().getHook(PapiHook.class);
62 | if (hook.isHooked())
63 | exprStr = hook.get().setPlaceholder(inputPlayer, exprStr);
64 |
65 | logger.debug("JS expression: " + exprStr);
66 | if (exprStr.isBlank()) {
67 | logger.debug("JS expression is blank");
68 | return false;
69 | }
70 |
71 | return evaluate(exprStr);
72 | }
73 |
74 | private boolean evaluate(String exprStr) {
75 | try {
76 | engine.put("BukkitPlayer", inputPlayer);
77 | logger.debug("Evaluating JS expression: " + exprStr);
78 | var result = engine.getEngineByName("JavaScript").eval(exprStr);
79 | if (result instanceof Boolean) {
80 | return (Boolean) result;
81 | } else {
82 | logger.debug("JS expression did not return a boolean");
83 | return false;
84 | }
85 | } catch (Exception e) {
86 | logger.debug("JS expression failed to evaluate: " + e.getMessage());
87 | return false;
88 | }
89 | }
90 |
91 | @Override
92 | public String alias() {
93 | return alias;
94 | }
95 |
96 | @Override
97 | public String messageOnFail() {
98 | return messageOnFail;
99 | }
100 |
101 | @Override
102 | public Player inputPlayer() {
103 | return inputPlayer;
104 | }
105 |
106 | @Override
107 | public Type getType() {
108 | return type;
109 | }
110 |
111 | @Override
112 | public void setType(Type type) {
113 | this.type = type;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/validators/NoopValidator.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt.validators;
2 |
3 | import com.cyr1en.commandprompter.api.prompt.InputValidator;
4 | import org.bukkit.entity.Player;
5 |
6 | /**
7 | * A validator that does nothing.
8 | *
9 | * This will always return true.
10 | */
11 | public class NoopValidator implements InputValidator {
12 | @Override
13 | public boolean validate(String input) {
14 | return true;
15 | }
16 |
17 | @Override
18 | public String alias() {
19 | return "noop";
20 | }
21 |
22 | @Override
23 | public String messageOnFail() {
24 | return "";
25 | }
26 |
27 | @Override
28 | public Player inputPlayer() {
29 | return null;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/validators/OnlinePlayerValidator.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt.validators;
2 |
3 | import com.cyr1en.commandprompter.api.prompt.CompoundableValidator;
4 | import com.cyr1en.commandprompter.api.prompt.InputValidator;
5 | import org.bukkit.Bukkit;
6 | import org.bukkit.entity.Player;
7 |
8 | /**
9 | * A validator that checks if the input is an online player.
10 | **/
11 | public class OnlinePlayerValidator implements InputValidator, CompoundableValidator {
12 |
13 |
14 | public static final CompoundableValidator.Type DEFAULT_TYPE = Type.AND;
15 | private CompoundableValidator.Type type = DEFAULT_TYPE;
16 |
17 | private final String alias;
18 | private final String messageOnFail;
19 | private final Player inputPlayer;
20 |
21 | public OnlinePlayerValidator(String alias, String messageOnFail, Player inputPlayer) {
22 | this.alias = alias;
23 | this.messageOnFail = messageOnFail;
24 | this.inputPlayer = inputPlayer;
25 | }
26 |
27 | @Override
28 | public boolean validate(String input) {
29 | if (input == null || input.isBlank())
30 | return false;
31 | var player = Bukkit.getPlayer(input);
32 | return player != null && player.isOnline();
33 | }
34 |
35 | @Override
36 | public String alias() {
37 | return this.alias;
38 | }
39 |
40 | @Override
41 | public String messageOnFail() {
42 | return this.messageOnFail;
43 | }
44 |
45 | @Override
46 | public Player inputPlayer() {
47 | return this.inputPlayer;
48 | }
49 |
50 | @Override
51 | public Type getType() {
52 | return this.type;
53 | }
54 |
55 | @Override
56 | public void setType(Type type) {
57 | this.type = type;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/prompt/validators/RegexValidator.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.prompt.validators;
2 |
3 | import com.cyr1en.commandprompter.api.prompt.CompoundableValidator;
4 | import com.cyr1en.commandprompter.api.prompt.InputValidator;
5 | import org.bukkit.entity.Player;
6 |
7 | import java.util.regex.Pattern;
8 |
9 | /**
10 | * A validator that uses regex to validate input.
11 | *
12 | * This validator is used to validate input based on a regex pattern.
13 | */
14 | public class RegexValidator implements InputValidator, CompoundableValidator {
15 |
16 | public static final CompoundableValidator.Type DEFAULT_TYPE = CompoundableValidator.Type.AND;
17 | private CompoundableValidator.Type type = DEFAULT_TYPE;
18 |
19 | private final String alias;
20 | private final Pattern regex;
21 | private final String messageOnFail;
22 | private final Player inputPlayer;
23 |
24 |
25 | public RegexValidator(String alias, Pattern regex, String messageOnFail, Player inputPlayer) {
26 | this.alias = alias;
27 | this.regex = regex;
28 | this.messageOnFail = messageOnFail;
29 | this.inputPlayer = inputPlayer;
30 | }
31 |
32 | @Override
33 | public boolean validate(String input) {
34 | if (input == null || regex == null)
35 | return false;
36 | return regex.matcher(input).matches();
37 | }
38 |
39 | @Override
40 | public String alias() {
41 | return this.alias;
42 | }
43 |
44 | @Override
45 | public String messageOnFail() {
46 | return this.messageOnFail;
47 | }
48 |
49 | @Override
50 | public Player inputPlayer() {
51 | return this.inputPlayer;
52 | }
53 |
54 | /**
55 | * Get the regex pattern.
56 | *
57 | * @return the regex pattern.
58 | */
59 | public Pattern regex() {
60 | return regex;
61 | }
62 |
63 | @Override
64 | public Type getType() {
65 | return this.type;
66 | }
67 |
68 | @Override
69 | public void setType(Type type) {
70 | this.type = type;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/util/MMUtil.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.util;
2 |
3 | import net.kyori.adventure.text.Component;
4 | import net.kyori.adventure.text.minimessage.MiniMessage;
5 | import net.kyori.adventure.text.minimessage.tag.standard.StandardTags;
6 |
7 | import java.util.List;
8 |
9 | public class MMUtil {
10 |
11 | public static List filterOutMiniMessageTags(List strs) {
12 | return strs.stream().filter(str -> !isMiniMessageTag(str)).toList();
13 | }
14 |
15 | /**
16 | * Function that checks if the prompt is a mini message tag.
17 | *
18 | * @param str Prompt to check
19 | * @return true if the prompt is a mini message tag, false otherwise.
20 | */
21 | public static boolean isMiniMessageTag(String str) {
22 | var serializer = MiniMessage.builder()
23 | .tags(StandardTags.defaults()).build();
24 | var parsed = serializer.deserialize(str);
25 | return !Component.text(str).equals(parsed);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/util/ServerUtil.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.util;
2 |
3 | import com.cyr1en.kiso.mc.Version;
4 | import org.bukkit.Bukkit;
5 |
6 | public class ServerUtil {
7 |
8 |
9 | public static boolean isAtOrAbove(String versionString) {
10 | var current = parsedVersion();
11 | var target = Version.parse(versionString);
12 | return current.isNewerThan(target) || current.equals(target);
13 | }
14 |
15 | public static String version() {
16 | return Bukkit.getServer().getVersion();
17 | }
18 |
19 | public static Version parsedVersion() {
20 | var version = version();
21 | version = version.substring(version.indexOf("MC: ") + 4, version.length() - 1);
22 | return Version.parse(version);
23 | }
24 |
25 | public static boolean isMojangMapped() {
26 | var resolved = ServerType.resolve();
27 | var ver = parsedVersion();
28 | return (resolved == ServerType.Paper || resolved == ServerType.Purpur) && ver.isNewerThan(Version.parse("1.20.4"));
29 | }
30 |
31 | public static boolean BUNGEE_CHAT_AVAILABLE() {
32 | try {
33 | Class.forName("net.md_5.bungee.api.ChatColor");
34 | return true;
35 | } catch (ClassNotFoundException e) {
36 | return false;
37 | }
38 | }
39 |
40 | public static ServerType resolve() {
41 | return ServerType.resolve();
42 | }
43 |
44 | /**
45 | * Pretty much useless as of now. But I'm keeping it just in case we need
46 | * some logic for different server types in the future.
47 | */
48 | public enum ServerType {
49 | CraftBukkit,
50 | Spigot,
51 | Paper,
52 | Purpur,
53 | CatServer,
54 | Mohist,
55 | Other;
56 |
57 | private static ServerType resolved;
58 |
59 | public static ServerType resolve() {
60 | if (resolved != null) return resolved;
61 |
62 | for (ServerType type : values()) {
63 | var typeName = type.name().toLowerCase();
64 | var serverName = Bukkit.getServer().getName().toLowerCase();
65 | if (serverName.contains(typeName)) {
66 | resolved = type;
67 | }
68 | }
69 | resolved = Other;
70 | return resolved;
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/util/Util.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.util;
2 |
3 | import com.cyr1en.commandprompter.CommandPrompter;
4 | import com.cyr1en.commandprompter.PluginLogger;
5 | import com.cyr1en.kiso.mc.Version;
6 | import com.google.common.base.Charsets;
7 | import com.google.common.io.CharStreams;
8 | import net.md_5.bungee.api.ChatColor;
9 |
10 | import org.bukkit.Material;
11 | import org.bukkit.plugin.Plugin;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.io.InputStreamReader;
16 | import java.nio.file.Files;
17 | import java.security.MessageDigest;
18 | import java.security.NoSuchAlgorithmException;
19 | import java.util.Locale;
20 | import java.util.Objects;
21 | import java.util.Optional;
22 | import java.util.function.Consumer;
23 | import java.util.function.Supplier;
24 | import java.util.regex.Pattern;
25 |
26 | public class Util {
27 |
28 | public static String stripColor(String msg) {
29 | return ChatColor.stripColor(color(msg));
30 | }
31 |
32 | private static Optional getLogger() {
33 | if (CommandPrompter.getInstance() == null)
34 | return Optional.empty();
35 | return Optional.of(CommandPrompter.getInstance().getPluginLogger());
36 | }
37 |
38 |
39 | public static String color(String msg) {
40 | if (!ServerUtil.BUNGEE_CHAT_AVAILABLE())
41 | return org.bukkit.ChatColor.translateAlternateColorCodes('&', msg);
42 |
43 | var supportedHex = ServerUtil.parsedVersion().isNewerThan(Version.parse("1.15.0"));
44 | if (supportedHex) {
45 | var pattern = Pattern.compile("#[a-fA-F0-9]{6}");
46 | var matcher = pattern.matcher(msg);
47 |
48 | while (matcher.find()) {
49 | String color = msg.substring(matcher.start(), matcher.end());
50 | msg = msg.replace(color, ChatColor.of(color) + "");
51 | matcher = pattern.matcher(msg);
52 | }
53 | }
54 | return ChatColor.translateAlternateColorCodes('&', msg);
55 | }
56 |
57 | public static Material getCheckedMaterial(String materialString, Material defaultMaterial) {
58 | materialString = materialString.toUpperCase(Locale.ROOT);
59 | var mat = Material.getMaterial(materialString);
60 | return Objects.isNull(mat) ? defaultMaterial : mat;
61 | }
62 |
63 | public static boolean checkSHA1(File file, String checksum) {
64 | try {
65 | var data = Files.readAllBytes(file.toPath());
66 | var hash = MessageDigest.getInstance("SHA-1").digest(data);
67 | var sb = new StringBuilder();
68 | for (byte b : hash)
69 | sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
70 | return sb.toString().equals(checksum);
71 | } catch (NoSuchAlgorithmException | IOException e) {
72 | return false;
73 | }
74 | }
75 |
76 | public static void deleteFile(File file, Consumer onFail) {
77 | if (file.exists())
78 | if (!file.delete())
79 | onFail.accept(file);
80 | }
81 |
82 | public static boolean isBundledVersion(Plugin plugin) {
83 | var is = plugin.getResource("META-INF/MANIFEST.MF");
84 | if (is == null) return false;
85 | try {
86 | var str = CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8));
87 | return str.contains("Bundled: true");
88 | } catch (IOException e) {
89 | return false;
90 | }
91 | }
92 |
93 | public static class ConsumerFallback {
94 |
95 | private final T val;
96 | private final Consumer consumer;
97 | private final Supplier test;
98 | private Runnable runnable;
99 |
100 | public ConsumerFallback(T val, Consumer consumer, Supplier test) {
101 | this.val = val;
102 | this.consumer = consumer;
103 | this.test = test;
104 | this.runnable = () -> {
105 | };
106 | }
107 |
108 | public void complete() {
109 | if (test.get()) consumer.accept(val);
110 | else runnable.run();
111 | }
112 |
113 | public ConsumerFallback orElse(Runnable runnable) {
114 | this.runnable = runnable;
115 | return this;
116 | }
117 | }
118 |
119 | }
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/util/unsafe/PvtFieldMutationException.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.util.unsafe;
2 |
3 | public class PvtFieldMutationException extends RuntimeException {
4 | public PvtFieldMutationException(Class> targetClass) {
5 | super("Failed to set '" + targetClass.getSimpleName() + "' to private target field");
6 | }
7 |
8 | public PvtFieldMutationException(String newVal) {
9 | super("Failed to set '" + newVal + "' to private target field");
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/cyr1en/commandprompter/util/unsafe/PvtFieldMutator.java:
--------------------------------------------------------------------------------
1 | package com.cyr1en.commandprompter.util.unsafe;
2 |
3 | import sun.misc.Unsafe;
4 |
5 | import java.util.Objects;
6 |
7 | /**
8 | * A utility class that allows you to easily change the value of private final fields.
9 | *
10 | * WARNING: Use this class with caution. Only use it for cases that justifies unsafe operations.
11 | * First, check if there's another way to accomplish
12 | *
13 | *
14 | * This class uses Sun's {@link Unsafe Unsafe} class to put a new {@link Object object}
15 | * on an {@link Object object's} private final encapsulated field.
16 | *
17 | * Usage:
18 | *
{@code
19 | * // let's say there's a private final field named "targetField" in instanceOfTarget
20 | * var instanceOfTarget = new Target();
21 | *
22 | * var newFieldVal = new SomeObject();
23 | *
24 | * var mutator = new PvtFieldMutator();
25 | * mutator.forField("targetField").in(instanceOfTarget).with(newFieldVal);
26 | * }
27 | */
28 | public class PvtFieldMutator {
29 |
30 | private final Unsafe unsafe;
31 | private String targetName;
32 | private Object targetInstance;
33 |
34 | /**
35 | * PvtFieldMutator default constructor.
36 | *
37 | *
38 | * Using classes in {@link java.lang.reflect reflect}, initialize the {@link Unsafe unsafe}
39 | * instance variable.
40 | *
41 | * @throws NoSuchFieldException when the private field "theUnsafe" is not found.
42 | * @throws IllegalAccessException when access to the field "theUnsafe" is prevented.
43 | */
44 | public PvtFieldMutator() throws NoSuchFieldException, IllegalAccessException {
45 | var unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
46 | unsafeField.setAccessible(true);
47 | this.unsafe = (Unsafe) unsafeField.get(null);
48 | }
49 |
50 | /**
51 | * Function that defines the name of the target.
52 | *
53 | * @param targetFieldName name of the target field.
54 | * @return instance of this class.
55 | */
56 | public PvtFieldMutator forField(String targetFieldName) {
57 | this.targetName = targetFieldName;
58 | return this;
59 | }
60 |
61 | /**
62 | * Function that defines the instance of the target object.
63 | *
64 | * @param objInstance instance of the target object.
65 | * @return instance of this class.
66 | */
67 | public PvtFieldMutator in(Object objInstance) {
68 | this.targetInstance = objInstance;
69 | return this;
70 | }
71 |
72 | /**
73 | * Function that defines the new object for the target field
74 | * and executes the mutation
75 | *
76 | * @param newObject object to replace the private final field with.
77 | * @throws NullPointerException when one of the target field name or target instance is null.
78 | * @throws NoSuchFieldException when the target field cannot be found in the target instance.
79 | */
80 | public void replaceWith(Object newObject) throws NoSuchFieldException, IllegalStateException, IllegalAccessException {
81 | assertTargetNotNull();
82 | var targetField = targetInstance.getClass().getDeclaredField(targetName);
83 | var targetFieldOffset = unsafe.objectFieldOffset(targetField);
84 | unsafe.putObject(targetInstance, targetFieldOffset, newObject);
85 |
86 | // Assert that we actually have it done correctly.
87 | targetField.setAccessible(true);
88 | var newFieldObject = targetField.get(targetInstance);
89 |
90 | if(Objects.isNull(newObject)) {
91 | if(!Objects.isNull(newFieldObject))
92 | throw new PvtFieldMutationException("null");
93 | return;
94 | }
95 |
96 | if (!newFieldObject.getClass().getCanonicalName().equals(newObject.getClass().getCanonicalName()))
97 | throw new PvtFieldMutationException(newObject.getClass());
98 | }
99 |
100 | /**
101 | * Helper function to easily get the current class name for the target field.
102 | *
103 | *
104 | * Uses reflection to set the target field accessible from the instance of the defining
105 | * object.
106 | *
107 | * @return The canonical name of the current class of the target field.
108 | * @throws NullPointerException when one of the target field name or target instance is null.
109 | * @throws NoSuchFieldException when the target field cannot be found in the target instance.
110 | */
111 | public String getClassName() throws NoSuchFieldException, IllegalAccessException {
112 | assertTargetNotNull();
113 | var targetField = targetInstance.getClass().getDeclaredField(targetName);
114 | targetField.setAccessible(true);
115 | var ob = targetField.get(targetInstance);
116 | return ob.getClass().getCanonicalName();
117 | }
118 |
119 | public int getHashCode() throws NoSuchFieldException, IllegalAccessException {
120 | assertTargetNotNull();
121 | var targetField = targetInstance.getClass().getDeclaredField(targetName);
122 | targetField.setAccessible(true);
123 | var ob = targetField.get(targetInstance);
124 | return ob.hashCode();
125 | }
126 |
127 | /**
128 | * Asserts that both target field name and target instance is not null.
129 | *
130 | * @throws NullPointerException when the target field cannot be found in the target instance.
131 | */
132 | private void assertTargetNotNull() throws IllegalStateException {
133 | if (this.targetInstance == null || this.targetName == null)
134 | throw new IllegalStateException("Target field name or target instance is null.");
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/resources/CommandPrompter.properties:
--------------------------------------------------------------------------------
1 | PromptInProgress = &6You are still trying to complete a command. Type &c"%s"&6 to cancel.
2 | SignPromptMultiArg = &6Sent a multi argument sign prompt. Type the response after the &a&l:
3 | SignPromptReminder = &6Sent a sign prompt. Every line that you put will be combined into a single response separated by a space
4 | CompletedCommand = &6Command to execute: &3%s
5 | PromptNoPerm = &cYou don't have permission to use CommandPrompter.
6 | PromptPlayerOnly = &6CommandPrompter can only be used by a player.!
7 | PromptCancel = &6Command completion cancelled!
8 | CommandCancelNotInCompletion = &cYou are not in command completion.
9 | CommandReloadSuccess = &aSuccessfully reloaded configuration!
10 | CommandRebuildHeadCacheSuccess = &aSuccessfully rebuilt head cache! (%sms)
11 | DelegateInvalidArgs = &cInvalid arguments for console delegate command!
12 | DelegateUsage = &6Usage: &3%s
13 | DelegateInvalidPlayer = &cThe player &6%s&c is not online!
14 | DelegateConsoleOnly = &cThis command can only be executed by the console!
15 | PluginVersion = &6Installed Version: &2&l%s
16 | PCMOutOfBounds = &cThe index &6%s&c is out of bounds!
--------------------------------------------------------------------------------
/src/main/resources/plugin.yml:
--------------------------------------------------------------------------------
1 | name: ${projectName}
2 | version: ${projectVersion}
3 | author: ${projectAuthor}
4 | description: ${projectDescription}
5 | softdepend: [SuperVanish,PremiumVanish, PlaceholderAPI, CarbonChat, Towny, HuskTowns]
6 |
7 | main: ${projectEntry}
8 | api-version: 1.13
9 |
10 | permissions:
11 | commandprompter.reload:
12 | description: Allow sender to reload plugin.
13 | commandprompter.use:
14 | description: Allow sender to use CommandPrompter argument feature.
15 | commandprompter.cancel:
16 | description: Allow sender to cancel their own command completion.
17 | commandprompter.consoledelegate:
18 | description: Allow sender to delegate command to console.
19 | commandprompter.rebuildheadcache:
20 | description: Allow sender to rebuild head cache.
--------------------------------------------------------------------------------