From 9ee4320482d0556d9b527ab9630e6ec86a3aad94 Mon Sep 17 00:00:00 2001 From: James Munson Date: Mon, 24 Jun 2019 05:30:26 -0700 Subject: [PATCH] Added !layout Signed-off-by: James Munson --- .../runelite/http/api/chat/ChatClient.java | 66 ++++++++++++++++ .../http/service/chat/ChatController.java | 68 ++++++++++++++++ .../http/service/chat/ChatService.java | 71 +++++++++++++++++ .../client/plugins/raids/RaidsPlugin.java | 78 +++++++++++++++++++ 4 files changed, 283 insertions(+) create mode 100644 http-service/src/main/java/net/runelite/http/service/chat/ChatController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/chat/ChatService.java diff --git a/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java b/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java index 2ab1d0c8ed..a1cf234e28 100644 --- a/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java +++ b/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java @@ -27,6 +27,8 @@ package net.runelite.http.api.chat; import com.google.gson.JsonParseException; import java.io.IOException; import java.io.InputStream; +import java.util.function.Predicate; +import java.util.regex.Pattern; import java.io.InputStreamReader; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; @@ -36,6 +38,10 @@ import okhttp3.Response; public class ChatClient { + private static final Predicate LAYOUT_VALIDATOR = Pattern + .compile("\\[[A-Z]+]:(\\s*\\w+\\s*(\\([A-Za-z]+\\))?,?)+") + .asPredicate(); + public boolean submitKc(String username, String boss, int kc) throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -258,4 +264,64 @@ public class ChatClient return Integer.parseInt(response.body().string()); } } + + public boolean submitLayout(String username, String layout) throws IOException + { + if (!testLayout(layout)) + { + throw new IOException("Layout " + layout + " is not valid!"); + } + + HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("chat") + .addPathSegment("layout") + .addQueryParameter("name", username) + .addQueryParameter("layout", layout) + .build(); + + Request request = new Request.Builder() + .post(RequestBody.create(null, new byte[0])) + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + { + return response.isSuccessful(); + } + } + + public String getLayout(String username) throws IOException + { + HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("chat") + .addPathSegment("layout") + .addQueryParameter("name", username) + .build(); + + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + throw new IOException("Unable to look up layout!"); + } + + final String layout = response.body().string(); + + if (!testLayout(layout)) + { + throw new IOException("Layout " + layout + " is not valid!"); + } + + return layout; + } + } + + public boolean testLayout(String layout) + { + return LAYOUT_VALIDATOR.test(layout); + } } diff --git a/http-service/src/main/java/net/runelite/http/service/chat/ChatController.java b/http-service/src/main/java/net/runelite/http/service/chat/ChatController.java new file mode 100644 index 0000000000..52f9a69ee9 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/chat/ChatController.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.chat; + +import java.util.regex.Pattern; +import com.google.common.base.Strings; +import net.runelite.http.service.util.exception.NotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/chat") +public class ChatController +{ + private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]"); + private static final int STRING_MAX_LENGTH = 50; + + @Autowired + private ChatService chatService; + + @PostMapping("/layout") + public void submitLayout(@RequestParam String name, @RequestParam String layout) + { + if (Strings.isNullOrEmpty(layout)) + { + return; + } + + chatService.setLayout(name, layout); + } + + @GetMapping("/layout") + public String getLayout(@RequestParam String name) + { + String layout = chatService.getLayout(name); + if (layout == null) + { + throw new NotFoundException(); + } + return layout; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/chat/ChatService.java b/http-service/src/main/java/net/runelite/http/service/chat/ChatService.java new file mode 100644 index 0000000000..66d895ccfb --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/chat/ChatService.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.chat; + +import java.time.Duration; +import net.runelite.http.api.chat.ChatClient; +import net.runelite.http.service.util.redis.RedisPool; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import redis.clients.jedis.Jedis; + +@Service +public class ChatService +{ + private static final Duration EXPIRE = Duration.ofMinutes(2); + + private final RedisPool jedisPool; + private final ChatClient chatClient = new ChatClient(); + + + @Autowired + public ChatService(RedisPool jedisPool) + { + this.jedisPool = jedisPool; + } + + public String getLayout(String name) + { + String value; + try (Jedis jedis = jedisPool.getResource()) + { + value = jedis.get("layout." + name); + } + return value; + } + + public void setLayout(String name, String layout) + { + if (!chatClient.testLayout(layout)) + { + throw new IllegalArgumentException(layout); + } + + try (Jedis jedis = jedisPool.getResource()) + { + jedis.setex("layout." + name, (int) EXPIRE.getSeconds(), layout); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java index a2d08ab61f..26d5db698c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java @@ -28,6 +28,7 @@ package net.runelite.client.plugins.raids; import com.google.common.collect.Lists; import com.google.inject.Binder; import com.google.inject.Provides; +import java.io.IOException; import java.awt.image.BufferedImage; import java.text.DecimalFormat; import java.time.Instant; @@ -70,11 +71,13 @@ import net.runelite.client.callback.ClientThread; import net.runelite.client.chat.ChatColorType; import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.ChatCommandManager; import net.runelite.client.chat.QueuedMessage; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.OverlayMenuClicked; import net.runelite.client.game.ItemManager; +import net.runelite.client.events.ChatInput; import net.runelite.client.game.SpriteManager; import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; @@ -96,6 +99,8 @@ import net.runelite.client.util.HotkeyListener; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.Text; import org.apache.commons.lang3.StringUtils; +import static net.runelite.client.util.Text.sanitize; +import net.runelite.http.api.chat.ChatClient; @PluginDescriptor( name = "Chambers Of Xeric", @@ -115,6 +120,7 @@ public class RaidsPlugin extends Plugin private static final String RAID_COMPLETE_MESSAGE = "Congratulations - your raid is complete!"; private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("###.##"); private static final String SPLIT_REGEX = "\\s*,\\s*"; + private static final String LAYOUT_COMMAND_STRING = "!layout"; private static final Pattern ROTATION_REGEX = Pattern.compile("\\[(.*?)]"); private static final int LINE_COMPONENT_HEIGHT = 16; private static final Pattern LEVEL_COMPLETE_REGEX = Pattern.compile("(.+) level complete! Duration: ([0-9:]+)"); @@ -139,6 +145,10 @@ public class RaidsPlugin extends Plugin @Inject private DrawManager drawManager; @Inject + private ChatCommandManager chatCommandManager; + @Inject + private ChatClient chatClient; + @Inject private ScheduledExecutorService executor; @Inject private RaidsConfig config; @@ -212,6 +222,7 @@ public class RaidsPlugin extends Plugin keyManager.registerKeyListener(hotkeyListener); updateLists(); clientThread.invokeLater(() -> checkRaidPresence(true)); + chatCommandManager.registerCommandAsync(LAYOUT_COMMAND_STRING, this::lookupRaid, this::submitRaidLookup); widgetOverlay = overlayManager.getWidgetOverlay(WidgetInfo.RAIDS_POINTS_INFOBOX); RaidsPanel panel = injector.getInstance(RaidsPanel.class); panel.init(config); @@ -242,6 +253,7 @@ public class RaidsPlugin extends Plugin raidStarted = false; raid = null; timer = null; + chatCommandManager.unregisterCommand(LAYOUT_COMMAND_STRING); final Widget widget = client.getWidget(WidgetInfo.RAIDS_POINTS_INFOBOX); if (widget != null) @@ -480,6 +492,72 @@ public class RaidsPlugin extends Plugin } } + private void lookupRaid(final ChatMessage chatmessage, final String message) + { + final String player; + if (chatmessage.getType().equals(ChatMessageType.PRIVATECHATOUT)) + { + player = client.getLocalPlayer().getName(); + } + else + { + player = sanitize(chatmessage.getName()); + } + + final String layout; + try + { + layout = chatClient.getLayout(player); + } + catch (IOException ex) + { + log.debug("unable to lookup raids layout", ex); + return; + } + + chatmessage.getMessageNode().setRuneLiteFormatMessage(new ChatMessageBuilder() + .append(ChatColorType.HIGHLIGHT) + .append("Layout: ") + .append(ChatColorType.NORMAL) + .append(layout) + .build()); + + chatMessageManager.update(chatmessage.getMessageNode()); + client.refreshChat(); + } + + private boolean submitRaidLookup(final ChatInput chatInput, final String value) + { + if (!inRaidChambers) + { + return true; + } + + final String playerName = sanitize(client.getLocalPlayer().getName()); + final String layout = getRaid().getLayout().toCodeString(); + final String rooms = getRaid().toRoomString(); + final String raidData = "[" + layout + "]: " + rooms; + log.debug("Submitting raids layout {} for {}", raidData, playerName); + + executor.execute(() -> + { + try + { + chatClient.submitLayout(playerName, raidData); + } + catch (IOException e) + { + log.warn("unable to submit raids layout", e); + } + finally + { + chatInput.resume(); + } + }); + + return true; + } + private void updatePartyMembers(boolean force) { int partySize = client.getVar(Varbits.RAID_PARTY_SIZE);