From 860aa89a4d12a0f73843796b6bf623759dae70fa Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 9 Feb 2019 11:41:30 -0500 Subject: [PATCH 1/2] http api: rename OSB ge classes with OSB prefix --- ...andExchangeClient.java => OSBGrandExchangeClient.java} | 6 +++--- ...andExchangeResult.java => OSBGrandExchangeResult.java} | 2 +- ...ngeController.java => OSBGrandExchangeController.java} | 6 +++--- ...dExchangeService.java => OSBGrandExchangeService.java} | 4 ++-- .../client/plugins/grandexchange/GrandExchangePlugin.java | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) rename http-api/src/main/java/net/runelite/http/api/osbuddy/{GrandExchangeClient.java => OSBGrandExchangeClient.java} (92%) rename http-api/src/main/java/net/runelite/http/api/osbuddy/{GrandExchangeResult.java => OSBGrandExchangeResult.java} (97%) rename http-service/src/main/java/net/runelite/http/service/osbuddy/{GrandExchangeController.java => OSBGrandExchangeController.java} (92%) rename http-service/src/main/java/net/runelite/http/service/osbuddy/{GrandExchangeService.java => OSBGrandExchangeService.java} (97%) diff --git a/http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeClient.java b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java similarity index 92% rename from http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeClient.java rename to http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java index d80a739d29..ffdfad2035 100644 --- a/http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeClient.java +++ b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java @@ -35,9 +35,9 @@ import okhttp3.Request; import okhttp3.Response; @Slf4j -public class GrandExchangeClient +public class OSBGrandExchangeClient { - public GrandExchangeResult lookupItem(int itemId) throws IOException + public OSBGrandExchangeResult lookupItem(int itemId) throws IOException { final HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() .addPathSegment("osb") @@ -59,7 +59,7 @@ public class GrandExchangeClient } final InputStream in = response.body().byteStream(); - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), GrandExchangeResult.class); + return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), OSBGrandExchangeResult.class); } catch (JsonParseException ex) { diff --git a/http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeResult.java b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeResult.java similarity index 97% rename from http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeResult.java rename to http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeResult.java index 993ee0d567..13e5f5e8e4 100644 --- a/http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeResult.java +++ b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeResult.java @@ -28,7 +28,7 @@ import java.time.Instant; import lombok.Data; @Data -public class GrandExchangeResult +public class OSBGrandExchangeResult { private int item_id; private int buy_average; diff --git a/http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeController.java b/http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeController.java similarity index 92% rename from http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeController.java rename to http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeController.java index 92ba9093e7..847c415772 100644 --- a/http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeController.java +++ b/http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeController.java @@ -35,12 +35,12 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/osb/ge") -public class GrandExchangeController +public class OSBGrandExchangeController { - private final GrandExchangeService grandExchangeService; + private final OSBGrandExchangeService grandExchangeService; @Autowired - public GrandExchangeController(GrandExchangeService grandExchangeService) + public OSBGrandExchangeController(OSBGrandExchangeService grandExchangeService) { this.grandExchangeService = grandExchangeService; } diff --git a/http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeService.java b/http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeService.java similarity index 97% rename from http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeService.java rename to http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeService.java index f7addee899..797d7e76e2 100644 --- a/http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeService.java +++ b/http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeService.java @@ -40,7 +40,7 @@ import org.sql2o.Sql2o; @Service @Slf4j -public class GrandExchangeService +public class OSBGrandExchangeService { private static final String CREATE_GRAND_EXCHANGE_PRICES = "CREATE TABLE IF NOT EXISTS `osb_ge` (\n" + " `item_id` int(11) NOT NULL,\n" @@ -56,7 +56,7 @@ public class GrandExchangeService private final Sql2o sql2o; @Autowired - public GrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o) + public OSBGrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o) { this.sql2o = sql2o; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index cd4f3f050e..c26e39ebb5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -73,8 +73,8 @@ import net.runelite.client.ui.NavigationButton; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.StackFormatter; import net.runelite.client.util.Text; -import net.runelite.http.api.osbuddy.GrandExchangeClient; -import net.runelite.http.api.osbuddy.GrandExchangeResult; +import net.runelite.http.api.osbuddy.OSBGrandExchangeClient; +import net.runelite.http.api.osbuddy.OSBGrandExchangeResult; @PluginDescriptor( name = "Grand Exchange", @@ -86,7 +86,7 @@ public class GrandExchangePlugin extends Plugin { private static final int OFFER_CONTAINER_ITEM = 21; private static final int OFFER_DEFAULT_ITEM_ID = 6512; - private static final GrandExchangeClient CLIENT = new GrandExchangeClient(); + private static final OSBGrandExchangeClient CLIENT = new OSBGrandExchangeClient(); private static final String OSB_GE_TEXT = "
OSBuddy Actively traded price: "; private static final String BUY_LIMIT_GE_TEXT = "
Buy limit: "; @@ -346,7 +346,7 @@ public class GrandExchangePlugin extends Plugin try { - final GrandExchangeResult result = CLIENT.lookupItem(itemId); + final OSBGrandExchangeResult result = CLIENT.lookupItem(itemId); final String text = geText.getText() + OSB_GE_TEXT + StackFormatter.formatNumber(result.getOverall_average()); geText.setText(text); } From da67381ba771869325adaf5ad6069209c71239f0 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 9 Feb 2019 11:42:40 -0500 Subject: [PATCH 2/2] Add GE history tracker Add http service to log completed trades, and submit trades when completed. --- .../http/api/ge/GrandExchangeClient.java | 78 ++++++++++ .../http/api/ge/GrandExchangeTrade.java | 38 +++++ .../service/ge/GrandExchangeController.java | 111 ++++++++++++++ .../http/service/ge/GrandExchangeService.java | 113 +++++++++++++++ .../runelite/http/service/ge/TradeAction.java | 31 ++++ .../runelite/http/service/ge/TradeEntry.java | 40 ++++++ .../grandexchange/GrandExchangePlugin.java | 136 +++++++++++++++++- .../plugins/grandexchange/SavedOffer.java | 39 +++++ 8 files changed, 583 insertions(+), 3 deletions(-) create mode 100644 http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java create mode 100644 http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/ge/TradeAction.java create mode 100644 http-service/src/main/java/net/runelite/http/service/ge/TradeEntry.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java diff --git a/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java new file mode 100644 index 0000000000..a1b97bc390 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019, 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.api.ge; + +import com.google.gson.Gson; +import java.io.IOException; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +@Slf4j +@AllArgsConstructor +public class GrandExchangeClient +{ + private static final MediaType JSON = MediaType.parse("application/json"); + private static final Gson GSON = RuneLiteAPI.GSON; + + private final UUID uuid; + + public void submit(GrandExchangeTrade grandExchangeTrade) + { + final HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("ge") + .build(); + + Request request = new Request.Builder() + .header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()) + .post(RequestBody.create(JSON, GSON.toJson(grandExchangeTrade))) + .url(url) + .build(); + + RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + log.debug("unable to submit trade", e); + } + + @Override + public void onResponse(Call call, Response response) + { + log.debug("Submitted trade"); + response.close(); + } + }); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java new file mode 100644 index 0000000000..b5d0012b16 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019, 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.api.ge; + +import java.time.Instant; +import lombok.Data; + +@Data +public class GrandExchangeTrade +{ + private boolean buy; + private int itemId; + private int quantity; + private int price; + private Instant time; +} diff --git a/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java new file mode 100644 index 0000000000..94c759b130 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019, 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.ge; + +import java.io.IOException; +import java.util.Collection; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.ge.GrandExchangeTrade; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/ge") +public class GrandExchangeController +{ + private final GrandExchangeService grandExchangeService; + private final AuthFilter authFilter; + + @Autowired + public GrandExchangeController(GrandExchangeService grandExchangeService, AuthFilter authFilter) + { + this.grandExchangeService = grandExchangeService; + this.authFilter = authFilter; + } + + @PostMapping + public void submit(HttpServletRequest request, HttpServletResponse response, @RequestBody GrandExchangeTrade grandExchangeTrade) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + grandExchangeService.add(session.getUser(), grandExchangeTrade); + } + + @GetMapping + public Collection get(HttpServletRequest request, HttpServletResponse response, + @RequestParam(required = false, defaultValue = "1024") int limit, + @RequestParam(required = false, defaultValue = "0") int offset) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return null; + } + + return grandExchangeService.get(session.getUser(), limit, offset).stream() + .map(GrandExchangeController::convert) + .collect(Collectors.toList()); + } + + private static GrandExchangeTrade convert(TradeEntry tradeEntry) + { + GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade(); + grandExchangeTrade.setBuy(tradeEntry.getAction() == TradeAction.BUY); + grandExchangeTrade.setItemId(tradeEntry.getItem()); + grandExchangeTrade.setQuantity(tradeEntry.getQuantity()); + grandExchangeTrade.setPrice(tradeEntry.getPrice()); + grandExchangeTrade.setTime(tradeEntry.getTime()); + return grandExchangeTrade; + } + + @DeleteMapping + public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + grandExchangeService.delete(session.getUser()); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeService.java b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeService.java new file mode 100644 index 0000000000..6456beb85f --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeService.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019, 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.ge; + +import java.util.Collection; +import net.runelite.http.api.ge.GrandExchangeTrade; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Sql2o; + +@Service +public class GrandExchangeService +{ + private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS `ge_trades` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `user` int(11) NOT NULL,\n" + + " `action` enum('BUY','SELL') NOT NULL,\n" + + " `item` int(11) NOT NULL,\n" + + " `quantity` int(11) NOT NULL,\n" + + " `price` int(11) NOT NULL,\n" + + " `time` timestamp NOT NULL DEFAULT current_timestamp(),\n" + + " PRIMARY KEY (`id`),\n" + + " KEY `user_time` (`user`, `time`),\n" + + " KEY `time` (`time`),\n" + + " CONSTRAINT `ge_trades_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`)\n" + + ") ENGINE=InnoDB;"; + + private final Sql2o sql2o; + + @Autowired + public GrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o) + { + this.sql2o = sql2o; + + // Ensure necessary tables exist + try (Connection con = sql2o.open()) + { + con.createQuery(CREATE_TABLE).executeUpdate(); + } + } + + public void add(int userId, GrandExchangeTrade grandExchangeTrade) + { + try (Connection con = sql2o.open()) + { + con.createQuery("insert into ge_trades (user, action, item, quantity, price) values (:user," + + " :action, :item, :quantity, :price)") + .addParameter("user", userId) + .addParameter("action", grandExchangeTrade.isBuy() ? "BUY" : "SELL") + .addParameter("item", grandExchangeTrade.getItemId()) + .addParameter("quantity", grandExchangeTrade.getQuantity()) + .addParameter("price", grandExchangeTrade.getPrice()) + .executeUpdate(); + } + } + + public Collection get(int userId, int limit, int offset) + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select id, user, action, item, quantity, price, time from ge_trades where user = :user limit :limit offset :offset") + .addParameter("user", userId) + .addParameter("limit", limit) + .addParameter("offset", offset) + .executeAndFetch(TradeEntry.class); + } + } + + public void delete(int userId) + { + try (Connection con = sql2o.open()) + { + con.createQuery("delete from ge_trades where user = :user") + .addParameter("user", userId) + .executeUpdate(); + } + } + + @Scheduled(fixedDelay = 60 * 60 * 1000) + public void expire() + { + try (Connection con = sql2o.open()) + { + con.createQuery("delete from ge_trades where time < current_timestamp - interval 1 month") + .executeUpdate(); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/ge/TradeAction.java b/http-service/src/main/java/net/runelite/http/service/ge/TradeAction.java new file mode 100644 index 0000000000..fcc96d615f --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/TradeAction.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019, 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.ge; + +enum TradeAction +{ + BUY, + SELL; +} diff --git a/http-service/src/main/java/net/runelite/http/service/ge/TradeEntry.java b/http-service/src/main/java/net/runelite/http/service/ge/TradeEntry.java new file mode 100644 index 0000000000..bca3869811 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/TradeEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019, 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.ge; + +import java.time.Instant; +import lombok.Data; + +@Data +class TradeEntry +{ + private int id; + private int user; + private TradeAction action; + private int item; + private int quantity; + private int price; + private Instant time; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index c26e39ebb5..5a59ede074 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -1,5 +1,5 @@ /* - * + * Copyright (c) 2019, Adam * Copyright (c) 2017, Robbie * Copyright (c) 2018, SomeoneWithAnInternetConnection * All rights reserved. @@ -46,6 +46,7 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.GrandExchangeOffer; +import net.runelite.api.GrandExchangeOfferState; import net.runelite.api.ItemComposition; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; @@ -56,11 +57,15 @@ import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.GrandExchangeOfferChanged; import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.SessionClose; +import net.runelite.api.events.SessionOpen; import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetID; import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.Notifier; +import net.runelite.client.account.AccountSession; +import net.runelite.client.account.SessionManager; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.ItemManager; @@ -73,6 +78,8 @@ import net.runelite.client.ui.NavigationButton; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.StackFormatter; import net.runelite.client.util.Text; +import net.runelite.http.api.ge.GrandExchangeClient; +import net.runelite.http.api.ge.GrandExchangeTrade; import net.runelite.http.api.osbuddy.OSBGrandExchangeClient; import net.runelite.http.api.osbuddy.OSBGrandExchangeResult; @@ -134,10 +141,38 @@ public class GrandExchangePlugin extends Plugin @Inject private ScheduledExecutorService executorService; + @Inject + private SessionManager sessionManager; + + @Inject + private ConfigManager configManager; + private Widget grandExchangeText; private Widget grandExchangeItem; private Map itemGELimits; + private GrandExchangeClient grandExchangeClient; + + private SavedOffer getOffer(int slot) + { + String offer = configManager.getConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot)); + if (offer == null) + { + return null; + } + return GSON.fromJson(offer, SavedOffer.class); + } + + private void setOffer(int slot, SavedOffer offer) + { + configManager.setConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot), GSON.toJson(offer)); + } + + private void deleteOffer(int slot) + { + configManager.unsetConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot)); + } + @Provides GrandExchangeConfig provideConfig(ConfigManager configManager) { @@ -167,6 +202,12 @@ public class GrandExchangePlugin extends Plugin mouseManager.registerMouseListener(inputListener); keyManager.registerKeyListener(inputListener); } + + AccountSession accountSession = sessionManager.getAccountSession(); + if (accountSession != null) + { + grandExchangeClient = new GrandExchangeClient(accountSession.getUuid()); + } } @Override @@ -178,6 +219,27 @@ public class GrandExchangePlugin extends Plugin grandExchangeText = null; grandExchangeItem = null; itemGELimits = null; + grandExchangeClient = null; + } + + @Subscribe + public void onSessionOpen(SessionOpen sessionOpen) + { + AccountSession accountSession = sessionManager.getAccountSession(); + if (accountSession.getUuid() != null) + { + grandExchangeClient = new GrandExchangeClient(accountSession.getUuid()); + } + else + { + grandExchangeClient = null; + } + } + + @Subscribe + public void onSessionClose(SessionClose sessionClose) + { + grandExchangeClient = null; } @Subscribe @@ -204,11 +266,79 @@ public class GrandExchangePlugin extends Plugin @Subscribe public void onGrandExchangeOfferChanged(GrandExchangeOfferChanged offerEvent) { - GrandExchangeOffer offer = offerEvent.getOffer(); + final int slot = offerEvent.getSlot(); + final GrandExchangeOffer offer = offerEvent.getOffer(); + ItemComposition offerItem = itemManager.getItemComposition(offer.getItemId()); boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1; BufferedImage itemImage = itemManager.getImage(offer.getItemId(), offer.getTotalQuantity(), shouldStack); - SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offerEvent.getOffer(), offerEvent.getSlot())); + SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offer, slot)); + + submitTrades(slot, offer); + + updateConfig(slot, offer); + } + + private void submitTrades(int slot, GrandExchangeOffer offer) + { + if (grandExchangeClient == null) + { + return; + } + + // Only interested in offers which are fully bought/sold + if (offer.getState() != GrandExchangeOfferState.BOUGHT && offer.getState() != GrandExchangeOfferState.SOLD) + { + return; + } + + SavedOffer savedOffer = getOffer(slot); + if (!shouldUpdate(savedOffer, offer)) + { + return; + } + + // getPrice() is the price of the offer, not necessarily what the item bought at + int priceEach = offer.getSpent() / offer.getTotalQuantity(); + + GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade(); + grandExchangeTrade.setBuy(offer.getState() == GrandExchangeOfferState.BOUGHT); + grandExchangeTrade.setItemId(offer.getItemId()); + grandExchangeTrade.setQuantity(offer.getTotalQuantity()); + grandExchangeTrade.setPrice(priceEach); + + log.debug("Submitting trade: {}", grandExchangeTrade); + grandExchangeClient.submit(grandExchangeTrade); + } + + private void updateConfig(int slot, GrandExchangeOffer offer) + { + if (offer.getState() == GrandExchangeOfferState.EMPTY) + { + deleteOffer(slot); + } + else + { + SavedOffer savedOffer = new SavedOffer(); + savedOffer.setItemId(offer.getItemId()); + savedOffer.setQuantitySold(offer.getQuantitySold()); + savedOffer.setTotalQuantity(offer.getTotalQuantity()); + savedOffer.setPrice(offer.getPrice()); + savedOffer.setSpent(offer.getSpent()); + savedOffer.setState(offer.getState()); + setOffer(slot, savedOffer); + } + } + + private boolean shouldUpdate(SavedOffer savedOffer, GrandExchangeOffer grandExchangeOffer) + { + if (savedOffer == null) + { + return false; + } + + // Only update offer if state has changed + return savedOffer.getState() != grandExchangeOffer.getState(); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java new file mode 100644 index 0000000000..58a4055fed --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019, 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.client.plugins.grandexchange; + +import lombok.Data; +import net.runelite.api.GrandExchangeOfferState; + +@Data +class SavedOffer +{ + private int itemId; + private int quantitySold; + private int totalQuantity; + private int price; + private int spent; + private GrandExchangeOfferState state; +}