001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.command;
020
021import com.google.inject.TypeLiteral;
022import com.plotsquared.core.configuration.caption.StaticCaption;
023import com.plotsquared.core.configuration.caption.TranslatableCaption;
024import com.plotsquared.core.permissions.Permission;
025import com.plotsquared.core.player.MetaDataAccess;
026import com.plotsquared.core.player.MetaDataKey;
027import com.plotsquared.core.player.PlotPlayer;
028import com.plotsquared.core.plot.Plot;
029import com.plotsquared.core.plot.comment.CommentInbox;
030import com.plotsquared.core.plot.comment.CommentManager;
031import com.plotsquared.core.plot.comment.PlotComment;
032import com.plotsquared.core.util.StringMan;
033import com.plotsquared.core.util.TabCompletions;
034import com.plotsquared.core.util.task.RunnableVal;
035import net.kyori.adventure.text.Component;
036import net.kyori.adventure.text.TextComponent;
037import net.kyori.adventure.text.minimessage.tag.Tag;
038import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
039
040import java.util.Collection;
041import java.util.Collections;
042import java.util.LinkedList;
043import java.util.List;
044import java.util.stream.Collectors;
045
046@CommandDeclaration(command = "inbox",
047        usage = "/plot inbox [inbox] [delete <index> | clear | page]",
048        permission = "plots.inbox",
049        category = CommandCategory.CHAT,
050        requiredType = RequiredType.PLAYER)
051public class Inbox extends SubCommand {
052
053    public void displayComments(PlotPlayer<?> player, List<PlotComment> oldComments, int page) {
054        if (oldComments == null || oldComments.isEmpty()) {
055            player.sendMessage(TranslatableCaption.of("comment.inbox_empty"));
056            return;
057        }
058        PlotComment[] comments = oldComments.toArray(new PlotComment[0]);
059        if (page < 0) {
060            page = 0;
061        }
062        // Get the total pages
063        // int totalPages = ((int) Math.ceil(12 *
064        int totalPages = (int) Math.ceil(comments.length / 12);
065        if (page > totalPages) {
066            page = totalPages;
067        }
068        // Only display 12 per page
069        int max = page * 12 + 12;
070        if (max > comments.length) {
071            max = comments.length;
072        }
073        TextComponent.Builder builder = Component.text();
074        builder.append(MINI_MESSAGE.deserialize(
075                TranslatableCaption.of("list.comment_list_header_paged").getComponent(player) + '\n',
076                TagResolver.builder()
077                        .tag("amount", Tag.inserting(Component.text(comments.length)))
078                        .tag("cur", Tag.inserting(Component.text(page + 1)))
079                        .tag("max", Tag.inserting(Component.text(totalPages + 1)))
080                        .tag("word", Tag.inserting(Component.text("all")))
081                        .build()
082        ));
083
084        // This might work xD
085        for (int x = page * 12; x < max; x++) {
086            PlotComment comment = comments[x];
087            Component commentColored;
088            if (player.getName().equals(comment.senderName())) {
089                commentColored = MINI_MESSAGE
090                        .deserialize(
091                                TranslatableCaption.of("list.comment_list_by_lister").getComponent(player),
092                                TagResolver.resolver("comment", Tag.inserting(Component.text(comment.comment())))
093                        );
094            } else {
095                commentColored = MINI_MESSAGE
096                        .deserialize(
097                                TranslatableCaption.of("list.comment_list_by_other").getComponent(player),
098                                TagResolver.resolver("comment", Tag.inserting(Component.text(comment.comment())))
099                        );
100            }
101            TagResolver resolver = TagResolver.builder()
102                    .tag("number", Tag.inserting(Component.text(x)))
103                    .tag("world", Tag.inserting(Component.text(comment.world())))
104                    .tag("plot_id", Tag.inserting(Component.text(comment.id().getX() + ";" + comment.id().getY())))
105                    .tag("commenter", Tag.inserting(Component.text(comment.senderName())))
106                    .tag("comment", Tag.inserting(commentColored))
107                    .build();
108            builder.append(MINI_MESSAGE
109                    .deserialize(
110                            TranslatableCaption.of("list.comment_list_comment").getComponent(player),
111                            resolver
112                    ));
113        }
114        player.sendMessage(StaticCaption.of(MINI_MESSAGE.serialize(builder.build())));
115    }
116
117    @Override
118    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
119        final Plot plot = player.getCurrentPlot();
120        if (plot == null) {
121            player.sendMessage(TranslatableCaption.of("errors.not_in_plot"));
122            return false;
123        }
124        if (!plot.hasOwner()) {
125            player.sendMessage(TranslatableCaption.of("info.plot_unowned"));
126            return false;
127        }
128        if (args.length == 0) {
129            sendUsage(player);
130            for (final CommentInbox inbox : CommentManager.inboxes.values()) {
131                if (inbox.canRead(plot, player)) {
132                    if (!inbox.getComments(plot, new RunnableVal<>() {
133                        @Override
134                        public void run(List<PlotComment> value) {
135                            if (value != null) {
136                                int total = 0;
137                                int unread = 0;
138                                for (PlotComment comment : value) {
139                                    total++;
140                                    if (comment.timestamp() > CommentManager
141                                            .getTimestamp(player, inbox.toString())) {
142                                        unread++;
143                                    }
144                                }
145                                if (total != 0) {
146                                    player.sendMessage(
147                                            TranslatableCaption.of("comment.inbox_item"),
148                                            TagResolver.resolver(
149                                                    "value",
150                                                    Tag.inserting(Component.text(inbox + " (" + total + '/' + unread + ')'))
151                                            )
152                                    );
153                                    return;
154                                }
155                            }
156                            player.sendMessage(
157                                    TranslatableCaption.of("comment.inbox_item"),
158                                    TagResolver.resolver("value", Tag.inserting(Component.text(inbox.toString())))
159                            );
160                        }
161                    })) {
162                        player.sendMessage(
163                                TranslatableCaption.of("comment.inbox_item"),
164                                TagResolver.resolver("value", Tag.inserting(Component.text(inbox.toString())))
165                        );
166                    }
167                }
168            }
169            return false;
170        }
171        final CommentInbox inbox = CommentManager.inboxes.get(args[0].toLowerCase());
172        if (inbox == null) {
173            player.sendMessage(
174                    TranslatableCaption.of("comment.invalid_inbox"),
175                    TagResolver.resolver(
176                            "list",
177                            Tag.inserting(Component.text(StringMan.join(CommentManager.inboxes.keySet(), ", ")))
178                    )
179            );
180            return false;
181        }
182        final MetaDataKey<Long> metaDataKey = MetaDataKey.of(
183                String.format("inbox:%s", inbox),
184                new TypeLiteral<>() {
185                }
186        );
187        try (final MetaDataAccess<Long> metaDataAccess = player.accessTemporaryMetaData(metaDataKey)) {
188            metaDataAccess.set(System.currentTimeMillis());
189        }
190        final int page;
191        if (args.length > 1) {
192            switch (args[1].toLowerCase()) {
193                case "delete" -> {
194                    if (!inbox.canModify(plot, player)) {
195                        player.sendMessage(TranslatableCaption.of("comment.no_perm_inbox_modify"));
196                        return false;
197                    }
198                    if (args.length != 3) {
199                        player.sendMessage(
200                                TranslatableCaption.of("commandconfig.command_syntax"),
201                                TagResolver.resolver(
202                                        "value",
203                                        Tag.inserting(Component.text("/plot inbox " + inbox + " delete <index>"))
204                                )
205                        );
206                        return true;
207                    }
208                    final int index;
209                    try {
210                        index = Integer.parseInt(args[2]);
211                        if (index < 1) {
212                            player.sendMessage(
213                                    TranslatableCaption.of("comment.not_valid_inbox_index"),
214                                    TagResolver.resolver("number", Tag.inserting(Component.text(index)))
215                            );
216                            return false;
217                        }
218                    } catch (NumberFormatException ignored) {
219                        player.sendMessage(
220                                TranslatableCaption.of("commandconfig.command_syntax"),
221                                TagResolver.resolver(
222                                        "value",
223                                        Tag.inserting(Component.text("/plot inbox " + inbox + " delete <index>"))
224                                )
225                        );
226                        return false;
227                    }
228                    if (!inbox.getComments(plot, new RunnableVal<>() {
229                        @Override
230                        public void run(List<PlotComment> value) {
231                            if (index > value.size()) {
232                                player.sendMessage(
233                                        TranslatableCaption.of("comment.not_valid_inbox_index"),
234                                        TagResolver.resolver("number", Tag.inserting(Component.text(index)))
235                                );
236                                return;
237                            }
238                            PlotComment comment = value.get(index - 1);
239                            inbox.removeComment(plot, comment);
240                            boolean success = plot.getPlotCommentContainer().removeComment(comment);
241                            if (success) {
242                                player.sendMessage(
243                                        TranslatableCaption.of("comment.comment_removed_success"),
244                                        TagResolver.resolver("value", Tag.inserting(Component.text(comment.comment())))
245                                );
246                            } else {
247                                player.sendMessage(
248                                        TranslatableCaption.of("comment.comment_removed_failure"));
249                            }
250                        }
251                    })) {
252                        player.sendMessage(TranslatableCaption.of("errors.not_in_plot"));
253                        return false;
254                    }
255                    return true;
256                }
257                case "clear" -> {
258                    if (!inbox.canModify(plot, player)) {
259                        player.sendMessage(TranslatableCaption.of("comment.no_perm_inbox_modify"));
260                    }
261                    inbox.clearInbox(plot);
262                    List<PlotComment> comments = plot.getPlotCommentContainer().getComments(inbox.toString());
263                    if (!comments.isEmpty()) {
264                        player.sendMessage(
265                                TranslatableCaption.of("comment.comment_removed_success"),
266                                TagResolver.resolver("value", Tag.inserting(Component.text("*")))
267                        );
268                        plot.getPlotCommentContainer().removeComments(comments);
269                    }
270                    return true;
271                }
272                default -> {
273                    try {
274                        page = Integer.parseInt(args[1]);
275                    } catch (NumberFormatException ignored) {
276                        sendUsage(player);
277                        return false;
278                    }
279                }
280            }
281        } else {
282            page = 1;
283        }
284        if (!inbox.canRead(plot, player)) {
285            player.sendMessage(TranslatableCaption.of("comment.no_perm_inbox"));
286            return false;
287        }
288        if (!inbox.getComments(plot, new RunnableVal<>() {
289            @Override
290            public void run(List<PlotComment> value) {
291                displayComments(player, value, page);
292            }
293        })) {
294            player.sendMessage(TranslatableCaption.of("info.plot_unowned"));
295            return false;
296        }
297        return true;
298    }
299
300    @Override
301    public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) {
302        if (args.length == 1) {
303            final List<String> completions = new LinkedList<>();
304            if (player.hasPermission(Permission.PERMISSION_INBOX_READ_OWNER)) {
305                completions.add("owner");
306            }
307            if (player.hasPermission(Permission.PERMISSION_INBOX_READ_PUBLIC)) {
308                completions.add("public");
309            }
310            if (player.hasPermission(Permission.PERMISSION_INBOX_READ_REPORT)) {
311                completions.add("report");
312            }
313            final List<Command> commands = completions.stream().filter(completion -> completion
314                            .toLowerCase()
315                            .startsWith(args[0].toLowerCase()))
316                    .map(completion -> new Command(null, true, completion, "", RequiredType.PLAYER, CommandCategory.CHAT) {
317                    }).collect(Collectors.toCollection(LinkedList::new));
318            if (player.hasPermission(Permission.PERMISSION_INBOX) && args[0].length() > 0) {
319                commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList()));
320            }
321            return commands;
322        }
323        return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList());
324    }
325
326}