001//
002// MIT License
003//
004// Copyright (c) 2021 Alexander Söderberg & Contributors
005//
006// Permission is hereby granted, free of charge, to any person obtaining a copy
007// of this software and associated documentation files (the "Software"), to deal
008// in the Software without restriction, including without limitation the rights
009// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010// copies of the Software, and to permit persons to whom the Software is
011// furnished to do so, subject to the following conditions:
012//
013// The above copyright notice and this permission notice shall be included in all
014// copies or substantial portions of the Software.
015//
016// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022// SOFTWARE.
023//
024package cloud.commandframework.jda.parsers;
025
026import cloud.commandframework.arguments.CommandArgument;
027import cloud.commandframework.arguments.parser.ArgumentParseResult;
028import cloud.commandframework.arguments.parser.ArgumentParser;
029import cloud.commandframework.context.CommandContext;
030import cloud.commandframework.exceptions.parsing.NoInputProvidedException;
031import net.dv8tion.jda.api.JDA;
032import net.dv8tion.jda.api.entities.User;
033import org.checkerframework.checker.nullness.qual.NonNull;
034import org.jetbrains.annotations.NotNull;
035
036import java.util.HashSet;
037import java.util.List;
038import java.util.Queue;
039import java.util.Set;
040
041/**
042 * Command Argument for {@link User}
043 *
044 * @param <C> Command sender type
045 * @since 1.1.0
046 */
047@SuppressWarnings("unused")
048public final class UserArgument<C> extends CommandArgument<C, User> {
049
050    private final Set<ParserMode> modes;
051
052    private UserArgument(
053            final boolean required, final @NonNull String name,
054            final @NonNull Set<ParserMode> modes
055    ) {
056        super(required, name, new UserParser<>(modes), User.class);
057        this.modes = modes;
058    }
059
060    /**
061     * Create a new builder
062     *
063     * @param name Name of the component
064     * @param <C>  Command sender type
065     * @return Created builder
066     */
067    public static <C> @NonNull Builder<C> newBuilder(final @NonNull String name) {
068        return new Builder<>(name);
069    }
070
071    /**
072     * Create a new required command component
073     *
074     * @param name Component name
075     * @param <C>  Command sender type
076     * @return Created component
077     */
078    public static <C> @NonNull CommandArgument<C, User> of(final @NonNull String name) {
079        return UserArgument.<C>newBuilder(name).withParserMode(ParserMode.MENTION).asRequired().build();
080    }
081
082    /**
083     * Create a new optional command component
084     *
085     * @param name Component name
086     * @param <C>  Command sender type
087     * @return Created component
088     */
089    public static <C> @NonNull CommandArgument<C, User> optional(final @NonNull String name) {
090        return UserArgument.<C>newBuilder(name).withParserMode(ParserMode.MENTION).asOptional().build();
091    }
092
093    /**
094     * Get the modes enabled on the parser
095     *
096     * @return List of Modes
097     */
098    public @NotNull Set<ParserMode> getModes() {
099        return modes;
100    }
101
102
103    public enum ParserMode {
104        MENTION,
105        ID,
106        NAME
107    }
108
109
110    public static final class Builder<C> extends CommandArgument.Builder<C, User> {
111
112        private Set<ParserMode> modes = new HashSet<>();
113
114        private Builder(final @NonNull String name) {
115            super(User.class, name);
116        }
117
118        /**
119         * Set the modes for the parsers to use
120         *
121         * @param modes List of Modes
122         * @return Builder instance
123         */
124        public @NonNull Builder<C> withParsers(final @NonNull Set<ParserMode> modes) {
125            this.modes = modes;
126            return this;
127        }
128
129        /**
130         * Add a parser mode to use
131         *
132         * @param mode Parser mode to add
133         * @return Builder instance
134         */
135        public @NonNull Builder<C> withParserMode(final @NonNull ParserMode mode) {
136            this.modes.add(mode);
137            return this;
138        }
139
140        /**
141         * Builder a new example component
142         *
143         * @return Constructed component
144         */
145        @Override
146        public @NonNull UserArgument<C> build() {
147            return new UserArgument<>(this.isRequired(), this.getName(), modes);
148        }
149
150    }
151
152
153    public static final class UserParser<C> implements ArgumentParser<C, User> {
154
155        private final Set<ParserMode> modes;
156
157        /**
158         * Construct a new argument parser for {@link User}
159         *
160         * @param modes List of parsing modes to use when parsing
161         * @throws java.lang.IllegalStateException If no parsing modes were provided
162         */
163        public UserParser(final @NonNull Set<ParserMode> modes) {
164            if (modes.isEmpty()) {
165                throw new IllegalArgumentException("At least one parsing mode is required");
166            }
167
168            this.modes = modes;
169        }
170
171        @Override
172        public @NonNull ArgumentParseResult<User> parse(
173                final @NonNull CommandContext<C> commandContext,
174                final @NonNull Queue<@NonNull String> inputQueue
175        ) {
176            final String input = inputQueue.peek();
177            if (input == null) {
178                return ArgumentParseResult.failure(new NoInputProvidedException(
179                        UserParser.class,
180                        commandContext
181                ));
182            }
183
184            final JDA jda = commandContext.get("JDA");
185            Exception exception = null;
186
187            if (modes.contains(ParserMode.MENTION)) {
188                if (input.startsWith("<@") && input.endsWith(">")) {
189                    final String id;
190                    if (input.startsWith("<@!")) {
191                        id = input.substring(3, input.length() - 1);
192                    } else {
193                        id = input.substring(2, input.length() - 1);
194                    }
195
196                    try {
197                        final ArgumentParseResult<User> result = this.userFromId(jda, input, id);
198                        inputQueue.remove();
199                        return result;
200                    } catch (final UserNotFoundParseException | NumberFormatException e) {
201                        exception = e;
202                    }
203                } else {
204                    exception = new IllegalArgumentException(
205                            String.format("Input '%s' is not a user mention.", input)
206                    );
207                }
208            }
209
210            if (modes.contains(ParserMode.ID)) {
211                try {
212                    final ArgumentParseResult<User> result = this.userFromId(jda, input, input);
213                    inputQueue.remove();
214                    return result;
215                } catch (final UserNotFoundParseException | NumberFormatException e) {
216                    exception = e;
217                }
218            }
219
220            if (modes.contains(ParserMode.NAME)) {
221                final List<User> users = jda.getUsersByName(input, true);
222
223                if (users.size() == 0) {
224                    exception = new UserNotFoundParseException(input);
225                } else if (users.size() > 1) {
226                    exception = new TooManyUsersFoundParseException(input);
227                } else {
228                    inputQueue.remove();
229                    return ArgumentParseResult.success(users.get(0));
230                }
231            }
232
233            assert exception != null;
234            return ArgumentParseResult.failure(exception);
235        }
236
237        @Override
238        public boolean isContextFree() {
239            return true;
240        }
241
242        private @NonNull ArgumentParseResult<User> userFromId(
243                final @NonNull JDA jda, final @NonNull String input,
244                final @NonNull String id
245        )
246                throws UserNotFoundParseException, NumberFormatException {
247            final User user = jda.getUserById(id);
248
249            if (user == null) {
250                throw new UserNotFoundParseException(input);
251            } else {
252                return ArgumentParseResult.success(user);
253            }
254        }
255
256    }
257
258
259    public static class UserParseException extends IllegalArgumentException {
260
261        private static final long serialVersionUID = -6728909884195850077L;
262        private final String input;
263
264        /**
265         * Construct a new user parse exception
266         *
267         * @param input String input
268         */
269        public UserParseException(final @NonNull String input) {
270            this.input = input;
271        }
272
273        /**
274         * Get the users input
275         *
276         * @return Users input
277         */
278        public final @NonNull String getInput() {
279            return input;
280        }
281
282    }
283
284
285    public static final class TooManyUsersFoundParseException extends UserParseException {
286
287        private static final long serialVersionUID = 7222089412615886672L;
288
289        /**
290         * Construct a new user parse exception
291         *
292         * @param input String input
293         */
294        public TooManyUsersFoundParseException(final @NonNull String input) {
295            super(input);
296        }
297
298        @Override
299        public @NonNull String getMessage() {
300            return String.format("Too many users found for '%s'.", getInput());
301        }
302
303    }
304
305
306    public static final class UserNotFoundParseException extends UserParseException {
307
308        private static final long serialVersionUID = 3689949065073643826L;
309
310        /**
311         * Construct a new user parse exception
312         *
313         * @param input String input
314         */
315        public UserNotFoundParseException(final @NonNull String input) {
316            super(input);
317        }
318
319        @Override
320        public @NonNull String getMessage() {
321            return String.format("User not found for '%s'.", getInput());
322        }
323
324    }
325
326}