From d7f72f79ebf005dc6330ed8413e8ff6c577ab55b Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 3 Jan 2022 02:19:20 +1100 Subject: [PATCH] Revert "Remove http-api and http-service" This reverts commit 055f5c2d --- http-api/pom.xml | 75 ++++ .../http/api/discord/DiscordClient.java | 6 +- .../http/api/account/OAuthResponse.java | 35 ++ .../net/runelite/http/api/chat/Duels.java | 36 ++ .../runelite/http/api/chat/LayoutRoom.java | 50 +++ .../java/net/runelite/http/api/chat/Task.java | 36 ++ .../runelite/http/api/config/ConfigEntry.java | 38 ++ .../http/api/config/Configuration.java | 37 ++ .../net/runelite/http/api/feed/FeedItem.java | 42 ++ .../runelite/http/api/feed/FeedItemType.java | 32 ++ .../runelite/http/api/feed/FeedResult.java | 36 ++ .../http/api/ge/GrandExchangeTrade.java | 48 +++ .../http/api/gson/ColorTypeAdapter.java | 89 +++++ .../api/gson/IllegalReflectionExclusion.java | 71 ++++ .../http/api/gson/InstantTypeAdapter.java | 83 ++++ .../http/api/item/ItemEquipmentStats.java | 57 +++ .../net/runelite/http/api/item/ItemPrice.java | 36 ++ .../net/runelite/http/api/item/ItemType.java | 48 +++ .../http/api/loottracker/GameItem.java | 38 ++ .../http/api/loottracker/LootAggregate.java | 44 +++ .../http/api/loottracker/LootRecord.java | 44 +++ .../http/api/loottracker/LootRecordType.java | 34 ++ .../net/runelite/http/api/worlds/World.java | 46 +++ .../runelite/http/api/worlds/WorldRegion.java | 74 ++++ .../runelite/http/api/worlds/WorldResult.java | 54 +++ .../runelite/http/api/worlds/WorldType.java | 39 ++ .../api/ws/RuntimeTypeAdapterFactory.java | 240 ++++++++++++ .../http/api/ws/WebsocketGsonFactory.java | 88 +++++ .../http/api/ws/WebsocketMessage.java | 35 ++ .../http/api/ws/messages/Handshake.java | 35 ++ .../http/api/ws/messages/LoginResponse.java | 37 ++ .../http/api/ws/messages/party/Join.java | 38 ++ .../http/api/ws/messages/party/Part.java | 31 ++ .../ws/messages/party/PartyChatMessage.java | 33 ++ .../ws/messages/party/PartyMemberMessage.java | 12 + .../api/ws/messages/party/PartyMessage.java | 35 ++ .../http/api/ws/messages/party/UserJoin.java | 39 ++ .../http/api/ws/messages/party/UserPart.java | 37 ++ .../http/api/ws/messages/party/UserSync.java | 34 ++ .../net/runelite/http/api/xtea/XteaKey.java | 34 ++ .../runelite/http/api/xtea/XteaRequest.java | 41 ++ .../http/api/gson/ColorTypeAdapterTest.java | 58 +++ .../http/api/gson/InstantTypeAdapterTest.java | 54 +++ http-service/pom.xml | 269 +++++++++++++ .../service/SpringBootWebApplication.java | 228 +++++++++++ .../service/SpringSchedulingConfigurer.java | 52 +++ .../http/service/SpringWebMvcConfigurer.java | 65 ++++ .../http/service/account/AccountService.java | 284 ++++++++++++++ .../http/service/account/AuthFilter.java | 116 ++++++ .../runelite/http/service/account/State.java | 35 ++ .../http/service/account/UserInfo.java | 46 +++ .../service/account/beans/SessionEntry.java | 76 ++++ .../http/service/account/beans/UserEntry.java | 57 +++ .../runelite/http/service/cache/CacheDAO.java | 123 ++++++ .../http/service/cache/CacheService.java | 254 ++++++++++++ .../service/cache/beans/ArchiveEntry.java | 38 ++ .../http/service/cache/beans/CacheEntry.java | 36 ++ .../http/service/cache/beans/FileEntry.java | 36 ++ .../http/service/cache/beans/IndexEntry.java | 36 ++ .../http/service/chat/ChatController.java | 274 +++++++++++++ .../http/service/chat/ChatService.java | 263 +++++++++++++ .../http/service/chat/KillCountKey.java | 34 ++ .../http/service/config/ConfigController.java | 134 +++++++ .../http/service/config/ConfigService.java | 367 ++++++++++++++++++ .../http/service/feed/FeedController.java | 137 +++++++ .../http/service/feed/blog/BlogService.java | 143 +++++++ .../feed/osrsnews/OSRSNewsService.java | 143 +++++++ .../twitter/TwitterOAuth2TokenResponse.java | 38 ++ .../service/feed/twitter/TwitterService.java | 183 +++++++++ .../twitter/TwitterStatusesResponseItem.java | 35 ++ .../TwitterStatusesResponseItemUser.java | 38 ++ .../service/ge/GrandExchangeController.java | 158 ++++++++ .../http/service/ge/GrandExchangeService.java | 119 ++++++ .../service/ge/GrandExchangeTradeHistory.java | 38 ++ .../net/runelite/http/service/ge/Trade.java | 51 +++ .../runelite/http/service/ge/TradeAction.java | 31 ++ .../runelite/http/service/ge/TradeEntry.java | 40 ++ .../http/service/item/ItemController.java | 113 ++++++ .../runelite/http/service/item/ItemEntry.java | 39 ++ .../http/service/item/ItemService.java | 287 ++++++++++++++ .../http/service/item/PriceEntry.java | 40 ++ .../runelite/http/service/item/RSItem.java | 36 ++ .../http/service/item/RSItemResponse.java | 33 ++ .../runelite/http/service/item/RSPrices.java | 37 ++ .../http/service/loottracker/LootResult.java | 42 ++ .../loottracker/LootTrackerController.java | 118 ++++++ .../loottracker/LootTrackerService.java | 259 ++++++++++++ .../pluginhub/PluginHubController.java | 144 +++++++ .../http/service/util/InstantConverter.java | 48 +++ .../InternalServerErrorException.java | 38 ++ .../util/exception/NotFoundException.java | 35 ++ .../http/service/util/redis/RedisPool.java | 102 +++++ .../http/service/wiki/PriceResult.java | 44 +++ .../http/service/wiki/WikiPriceService.java | 140 +++++++ .../http/service/worlds/ServiceWorldType.java | 60 +++ .../http/service/worlds/WorldController.java | 68 ++++ .../http/service/worlds/WorldsService.java | 132 +++++++ .../runelite/http/service/xtea/XteaCache.java | 39 ++ .../http/service/xtea/XteaController.java | 86 ++++ .../runelite/http/service/xtea/XteaEntry.java | 40 ++ .../http/service/xtea/XteaService.java | 250 ++++++++++++ .../src/main/resources/application-dev.yaml | 29 ++ .../src/main/resources/application.yaml | 61 +++ http-service/src/main/templates/markdown.hbs | 110 ++++++ http-service/src/main/templates/operation.hbs | 71 ++++ http-service/src/main/templates/security.hbs | 88 +++++ .../src/main/templates/template.html.hbs | 49 +++ http-service/src/main/webapp/WEB-INF/web.xml | 32 ++ .../service/config/ConfigControllerTest.java | 85 ++++ .../service/config/ConfigServiceTest.java | 72 ++++ .../LootTrackerControllerTest.java | 104 +++++ .../service/worlds/WorldsServiceTest.java | 79 ++++ .../src/test/resources/application-test.yaml | 21 + .../runelite/http/service/worlds/worldlist | Bin 0 -> 5000 bytes .../client/plugins/xtea/XteaClient.java | 29 +- 115 files changed, 9128 insertions(+), 18 deletions(-) create mode 100644 http-api/pom.xml create mode 100644 http-api/src/main/java/net/runelite/http/api/account/OAuthResponse.java create mode 100644 http-api/src/main/java/net/runelite/http/api/chat/Duels.java create mode 100644 http-api/src/main/java/net/runelite/http/api/chat/LayoutRoom.java create mode 100644 http-api/src/main/java/net/runelite/http/api/chat/Task.java create mode 100644 http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java create mode 100644 http-api/src/main/java/net/runelite/http/api/config/Configuration.java create mode 100644 http-api/src/main/java/net/runelite/http/api/feed/FeedItem.java create mode 100644 http-api/src/main/java/net/runelite/http/api/feed/FeedItemType.java create mode 100644 http-api/src/main/java/net/runelite/http/api/feed/FeedResult.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java create mode 100644 http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java create mode 100644 http-api/src/main/java/net/runelite/http/api/gson/IllegalReflectionExclusion.java create mode 100644 http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java create mode 100644 http-api/src/main/java/net/runelite/http/api/item/ItemEquipmentStats.java create mode 100644 http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java create mode 100644 http-api/src/main/java/net/runelite/http/api/item/ItemType.java create mode 100644 http-api/src/main/java/net/runelite/http/api/loottracker/GameItem.java create mode 100644 http-api/src/main/java/net/runelite/http/api/loottracker/LootAggregate.java create mode 100644 http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java create mode 100644 http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java create mode 100644 http-api/src/main/java/net/runelite/http/api/worlds/World.java create mode 100644 http-api/src/main/java/net/runelite/http/api/worlds/WorldRegion.java create mode 100644 http-api/src/main/java/net/runelite/http/api/worlds/WorldResult.java create mode 100644 http-api/src/main/java/net/runelite/http/api/worlds/WorldType.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/RuntimeTypeAdapterFactory.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/WebsocketGsonFactory.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/WebsocketMessage.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/Handshake.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/LoginResponse.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/party/Part.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyChatMessage.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMemberMessage.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMessage.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java create mode 100644 http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java create mode 100644 http-api/src/main/java/net/runelite/http/api/xtea/XteaKey.java create mode 100644 http-api/src/main/java/net/runelite/http/api/xtea/XteaRequest.java create mode 100644 http-api/src/test/java/net/runelite/http/api/gson/ColorTypeAdapterTest.java create mode 100644 http-api/src/test/java/net/runelite/http/api/gson/InstantTypeAdapterTest.java create mode 100644 http-service/pom.xml create mode 100644 http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java create mode 100644 http-service/src/main/java/net/runelite/http/service/SpringSchedulingConfigurer.java create mode 100644 http-service/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/AccountService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/State.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/UserInfo.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/beans/SessionEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/account/beans/UserEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/cache/CacheDAO.java create mode 100644 http-service/src/main/java/net/runelite/http/service/cache/CacheService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/cache/beans/ArchiveEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/cache/beans/CacheEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/cache/beans/FileEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/cache/beans/IndexEntry.java 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 create mode 100644 http-service/src/main/java/net/runelite/http/service/chat/KillCountKey.java create mode 100644 http-service/src/main/java/net/runelite/http/service/config/ConfigController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/config/ConfigService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/feed/FeedController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/feed/blog/BlogService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/feed/osrsnews/OSRSNewsService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterOAuth2TokenResponse.java create mode 100644 http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterStatusesResponseItem.java create mode 100644 http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterStatusesResponseItemUser.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/GrandExchangeTradeHistory.java create mode 100644 http-service/src/main/java/net/runelite/http/service/ge/Trade.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 http-service/src/main/java/net/runelite/http/service/item/ItemController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/item/ItemEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/item/ItemService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/item/RSItem.java create mode 100644 http-service/src/main/java/net/runelite/http/service/item/RSItemResponse.java create mode 100644 http-service/src/main/java/net/runelite/http/service/item/RSPrices.java create mode 100644 http-service/src/main/java/net/runelite/http/service/loottracker/LootResult.java create mode 100644 http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/pluginhub/PluginHubController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/util/InstantConverter.java create mode 100644 http-service/src/main/java/net/runelite/http/service/util/exception/InternalServerErrorException.java create mode 100644 http-service/src/main/java/net/runelite/http/service/util/exception/NotFoundException.java create mode 100644 http-service/src/main/java/net/runelite/http/service/util/redis/RedisPool.java create mode 100644 http-service/src/main/java/net/runelite/http/service/wiki/PriceResult.java create mode 100644 http-service/src/main/java/net/runelite/http/service/wiki/WikiPriceService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/worlds/ServiceWorldType.java create mode 100644 http-service/src/main/java/net/runelite/http/service/worlds/WorldController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/worlds/WorldsService.java create mode 100644 http-service/src/main/java/net/runelite/http/service/xtea/XteaCache.java create mode 100644 http-service/src/main/java/net/runelite/http/service/xtea/XteaController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/xtea/XteaEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java create mode 100644 http-service/src/main/resources/application-dev.yaml create mode 100644 http-service/src/main/resources/application.yaml create mode 100644 http-service/src/main/templates/markdown.hbs create mode 100644 http-service/src/main/templates/operation.hbs create mode 100644 http-service/src/main/templates/security.hbs create mode 100644 http-service/src/main/templates/template.html.hbs create mode 100644 http-service/src/main/webapp/WEB-INF/web.xml create mode 100644 http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java create mode 100644 http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java create mode 100644 http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java create mode 100644 http-service/src/test/java/net/runelite/http/service/worlds/WorldsServiceTest.java create mode 100644 http-service/src/test/resources/application-test.yaml create mode 100644 http-service/src/test/resources/net/runelite/http/service/worlds/worldlist diff --git a/http-api/pom.xml b/http-api/pom.xml new file mode 100644 index 0000000000..6f4e9a635e --- /dev/null +++ b/http-api/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + net.runelite + runelite-parent + 1.8.8-SNAPSHOT + + + Web API + http-api + + + + com.squareup.okhttp3 + okhttp + 3.14.9 + + + com.google.code.gson + gson + + + org.slf4j + slf4j-api + + + org.projectlombok + lombok + provided + + + + junit + junit + 4.12 + test + + + org.slf4j + slf4j-simple + test + + + com.squareup.okhttp3 + mockwebserver + 3.14.9 + test + + + diff --git a/http-api/src/main/java/com/openosrs/http/api/discord/DiscordClient.java b/http-api/src/main/java/com/openosrs/http/api/discord/DiscordClient.java index 61383bbbc4..47b82fdf55 100644 --- a/http-api/src/main/java/com/openosrs/http/api/discord/DiscordClient.java +++ b/http-api/src/main/java/com/openosrs/http/api/discord/DiscordClient.java @@ -29,7 +29,7 @@ package com.openosrs.http.api.discord; import com.google.gson.Gson; import java.io.IOException; import lombok.extern.slf4j.Slf4j; -import net.runelite.http.api.RuneLiteAPI; +//import net.runelite.http.api.RuneLiteAPI; import okhttp3.Call; import okhttp3.Callback; import okhttp3.HttpUrl; @@ -60,7 +60,7 @@ public class DiscordClient log.debug("Attempting to message with {}", discordMessage); - RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback() + /* RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback() { @Override @@ -95,6 +95,6 @@ public class DiscordClient log.debug("Submitted discord log record"); } } - }); + });*/ } } diff --git a/http-api/src/main/java/net/runelite/http/api/account/OAuthResponse.java b/http-api/src/main/java/net/runelite/http/api/account/OAuthResponse.java new file mode 100644 index 0000000000..930540b6eb --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/account/OAuthResponse.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 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.account; + +import java.util.UUID; +import lombok.Data; + +@Data +public class OAuthResponse +{ + private String oauthUrl; + private UUID uid; +} diff --git a/http-api/src/main/java/net/runelite/http/api/chat/Duels.java b/http-api/src/main/java/net/runelite/http/api/chat/Duels.java new file mode 100644 index 0000000000..ba117a526a --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/chat/Duels.java @@ -0,0 +1,36 @@ +/* + * 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.chat; + +import lombok.Data; + +@Data +public class Duels +{ + private int wins; + private int losses; + private int winningStreak; + private int losingStreak; +} diff --git a/http-api/src/main/java/net/runelite/http/api/chat/LayoutRoom.java b/http-api/src/main/java/net/runelite/http/api/chat/LayoutRoom.java new file mode 100644 index 0000000000..3498f83eed --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/chat/LayoutRoom.java @@ -0,0 +1,50 @@ +/* + * 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.chat; + +public enum LayoutRoom +{ + START, + END, + SCAVENGERS, + FARMING, + EMPTY, + + TEKTON, + MUTTADILES, + GUARDIANS, + VESPULA, + SHAMANS, + VASA, + VANGUARDS, + MYSTICS, + UNKNOWN_COMBAT, + + CRABS, + ICE_DEMON, + TIGHTROPE, + THIEVING, + UNKNOWN_PUZZLE; +} diff --git a/http-api/src/main/java/net/runelite/http/api/chat/Task.java b/http-api/src/main/java/net/runelite/http/api/chat/Task.java new file mode 100644 index 0000000000..db38c90109 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/chat/Task.java @@ -0,0 +1,36 @@ +/* + * 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.chat; + +import lombok.Data; + +@Data +public class Task +{ + private String task; + private int amount; + private int initialAmount; + private String location; +} diff --git a/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java b/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java new file mode 100644 index 0000000000..d8b69dae72 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 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.config; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ConfigEntry +{ + private String key; + private String value; +} diff --git a/http-api/src/main/java/net/runelite/http/api/config/Configuration.java b/http-api/src/main/java/net/runelite/http/api/config/Configuration.java new file mode 100644 index 0000000000..44269b735a --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/config/Configuration.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, 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.config; + +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class Configuration +{ + private List config = new ArrayList<>(); +} diff --git a/http-api/src/main/java/net/runelite/http/api/feed/FeedItem.java b/http-api/src/main/java/net/runelite/http/api/feed/FeedItem.java new file mode 100644 index 0000000000..620f639d15 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/feed/FeedItem.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +@AllArgsConstructor +public class FeedItem +{ + private final FeedItemType type; + private String avatar; + private final String title; + private final String content; + private final String url; + private final long timestamp; +} diff --git a/http-api/src/main/java/net/runelite/http/api/feed/FeedItemType.java b/http-api/src/main/java/net/runelite/http/api/feed/FeedItemType.java new file mode 100644 index 0000000000..e3cc9443cd --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/feed/FeedItemType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed; + +public enum FeedItemType +{ + BLOG_POST, + TWEET, + OSRS_NEWS +} diff --git a/http-api/src/main/java/net/runelite/http/api/feed/FeedResult.java b/http-api/src/main/java/net/runelite/http/api/feed/FeedResult.java new file mode 100644 index 0000000000..cf862b5be0 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/feed/FeedResult.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class FeedResult +{ + private List items; +} 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..fd6f6635d3 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java @@ -0,0 +1,48 @@ +/* + * 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; +import net.runelite.http.api.worlds.WorldType; + +@Data +public class GrandExchangeTrade +{ + private boolean buy; + private boolean cancel; + private boolean login; + private int itemId; + private int qty; + private int dqty; + private int total; + private int spent; + private int dspent; + private int offer; + private int slot; + private WorldType worldType; + private int seq; + private Instant resetTime; +} diff --git a/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java b/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java new file mode 100644 index 0000000000..feec0f6d66 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020 Abex + * 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.gson; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.awt.Color; +import java.io.IOException; + +public class ColorTypeAdapter extends TypeAdapter +{ + @Override + public void write(JsonWriter out, Color value) throws IOException + { + if (value == null) + { + out.nullValue(); + return; + } + + int rgba = value.getRGB(); + out.value(String.format("#%08X", rgba)); + } + + @Override + public Color read(JsonReader in) throws IOException + { + switch (in.peek()) + { + case NULL: + in.nextNull(); + return null; + case STRING: + { + String value = in.nextString(); + if (value.charAt(0) == '#') + { + value = value.substring(1); + } + int intValue = Integer.parseUnsignedInt(value, 16); + return new Color(intValue, true); + } + case BEGIN_OBJECT: + { + in.beginObject(); + double value = 0; + while (in.peek() != JsonToken.END_OBJECT) + { + switch (in.nextName()) + { + case "value": + value = in.nextDouble(); + break; + default: + in.skipValue(); + break; + } + } + in.endObject(); + return new Color((int) value, true); + } + } + return null; // throws + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/gson/IllegalReflectionExclusion.java b/http-api/src/main/java/net/runelite/http/api/gson/IllegalReflectionExclusion.java new file mode 100644 index 0000000000..a25ffb79f7 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/gson/IllegalReflectionExclusion.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 Abex + * 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.gson; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +public class IllegalReflectionExclusion implements ExclusionStrategy +{ + private static final Set PRIVATE_CLASSLOADERS = new HashSet<>(); + + static + { + for (ClassLoader cl = ClassLoader.getSystemClassLoader(); cl != null; ) + { + cl = cl.getParent(); + PRIVATE_CLASSLOADERS.add(cl); + } + } + + @Override + public boolean shouldSkipField(FieldAttributes f) + { + if (!PRIVATE_CLASSLOADERS.contains(f.getDeclaringClass().getClassLoader())) + { + return false; + } + + assert !Modifier.isPrivate(f.getDeclaringClass().getModifiers()) : "gsoning private class " + f.getDeclaringClass().getName(); + try + { + f.getDeclaringClass().getField(f.getName()); + } + catch (NoSuchFieldException e) + { + throw new AssertionError("gsoning private field " + f.getDeclaringClass() + "." + f.getName()); + } + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) + { + return false; + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java b/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java new file mode 100644 index 0000000000..d5162c0e01 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020 Abex + * 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.gson; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.time.Instant; + +// Just add water! +public class InstantTypeAdapter extends TypeAdapter +{ + @Override + public void write(JsonWriter out, Instant value) throws IOException + { + if (value == null) + { + out.nullValue(); + return; + } + + out.value(value.toEpochMilli()); + } + + @Override + public Instant read(JsonReader in) throws IOException + { + if (in.peek() == JsonToken.NULL) + { + in.nextNull(); + return null; + } + + if (in.peek() == JsonToken.NUMBER) + { + long jsTime = in.nextLong(); + return Instant.ofEpochMilli(jsTime); + } + + long seconds = 0; + int nanos = 0; + in.beginObject(); + while (in.peek() != JsonToken.END_OBJECT) + { + switch (in.nextName()) + { + case "nanos": + nanos = in.nextInt(); + break; + case "seconds": + seconds = in.nextLong(); + break; + } + } + in.endObject(); + + return Instant.ofEpochSecond(seconds, nanos); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemEquipmentStats.java b/http-api/src/main/java/net/runelite/http/api/item/ItemEquipmentStats.java new file mode 100644 index 0000000000..5848513c00 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemEquipmentStats.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.item; + +import com.google.gson.annotations.SerializedName; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class ItemEquipmentStats +{ + private int slot; + + @SerializedName("is2h") + private boolean isTwoHanded; + + private int astab; + private int aslash; + private int acrush; + private int amagic; + private int arange; + + private int dstab; + private int dslash; + private int dcrush; + private int dmagic; + private int drange; + + private int str; + private int rstr; + private int mdmg; + private int prayer; + private int aspeed; +} diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java b/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java new file mode 100644 index 0000000000..faf5bfc3fb --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 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.item; + +import lombok.Data; + +@Data +public class ItemPrice +{ + private int id; + private String name; + private int price; + private int wikiPrice; +} diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemType.java b/http-api/src/main/java/net/runelite/http/api/item/ItemType.java new file mode 100644 index 0000000000..9221f096b2 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemType.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 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.item; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public enum ItemType +{ + DEFAULT; + + private static final Logger logger = LoggerFactory.getLogger(ItemType.class); + + public static ItemType of(String type) + { + try + { + return ItemType.valueOf(type.toUpperCase()); + } + catch (IllegalArgumentException ex) + { + logger.warn("unable to convert type", ex); + return DEFAULT; + } + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/GameItem.java b/http-api/src/main/java/net/runelite/http/api/loottracker/GameItem.java new file mode 100644 index 0000000000..7e9b06c21b --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/GameItem.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, TheStonedTurtle + * 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.loottracker; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class GameItem +{ + private int id; + private int qty; +} diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootAggregate.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootAggregate.java new file mode 100644 index 0000000000..a5437226cc --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootAggregate.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020, 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.loottracker; + +import java.time.Instant; +import java.util.Collection; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class LootAggregate +{ + private String eventId; + private LootRecordType type; + private Collection drops; + private Instant first_time; + private Instant last_time; + private int amount; +} diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java new file mode 100644 index 0000000000..a8e04aefd3 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, TheStonedTurtle + * 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.loottracker; + +import java.time.Instant; +import java.util.Collection; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LootRecord +{ + private String eventId; + private LootRecordType type; + private Object metadata; + private Collection drops; + private Instant time; + private Integer world; +} diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java new file mode 100644 index 0000000000..33e622de45 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, TheStonedTurtle + * 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.loottracker; + +public enum LootRecordType +{ + NPC, + PLAYER, + EVENT, + PICKPOCKET, + UNKNOWN +} diff --git a/http-api/src/main/java/net/runelite/http/api/worlds/World.java b/http-api/src/main/java/net/runelite/http/api/worlds/World.java new file mode 100644 index 0000000000..70bc2c8237 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/worlds/World.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, 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.worlds; + +import java.util.EnumSet; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class World +{ + private int id; + private EnumSet types; + private String address; + private String activity; + private int location; + private int players; + + public WorldRegion getRegion() + { + return WorldRegion.valueOf(location); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/worlds/WorldRegion.java b/http-api/src/main/java/net/runelite/http/api/worlds/WorldRegion.java new file mode 100644 index 0000000000..2c391e91f4 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/worlds/WorldRegion.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020, melky + * 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.worlds; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Holds the data for each world's region (location) + */ +@AllArgsConstructor +@Getter +public enum WorldRegion +{ + UNITED_STATES_OF_AMERICA("US", "USA"), + UNITED_KINGDOM("GB", "GBR"), + AUSTRALIA("AU", "AUS"), + GERMANY("DE", "DEU"); + + /** + * ISO-3166-1 alpha-2 country code + */ + private final String alpha2; + /** + * ISO-3166-1 alpha-3 country code + */ + private final String alpha3; + + /** + * Gets the region using the location id + * {@link WorldRegion} value. + * + * @param locationId the location id of world + * @return WorldRegion the region of the world + */ + public static WorldRegion valueOf(int locationId) + { + switch (locationId) + { + case 0: + return UNITED_STATES_OF_AMERICA; + case 1: + return UNITED_KINGDOM; + case 3: + return AUSTRALIA; + case 7: + return GERMANY; + default: + return null; + } + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/worlds/WorldResult.java b/http-api/src/main/java/net/runelite/http/api/worlds/WorldResult.java new file mode 100644 index 0000000000..a4afe23e85 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/worlds/WorldResult.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017, 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.worlds; + +import java.util.List; + +public class WorldResult +{ + private List worlds; + + public List getWorlds() + { + return worlds; + } + + public void setWorlds(List worlds) + { + this.worlds = worlds; + } + + public World findWorld(int worldNum) + { + for (World world : worlds) + { + if (world.getId() == worldNum) + { + return world; + } + } + return null; + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/worlds/WorldType.java b/http-api/src/main/java/net/runelite/http/api/worlds/WorldType.java new file mode 100644 index 0000000000..20639b5f3c --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/worlds/WorldType.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Lotto + * 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.worlds; + +public enum WorldType +{ + MEMBERS, + PVP, + BOUNTY, + SKILL_TOTAL, + HIGH_RISK, + LAST_MAN_STANDING, + NOSAVE_MODE, + DEADMAN, + TOURNAMENT, + SEASONAL; +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/RuntimeTypeAdapterFactory.java b/http-api/src/main/java/net/runelite/http/api/ws/RuntimeTypeAdapterFactory.java new file mode 100644 index 0000000000..2819c92ceb --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/RuntimeTypeAdapterFactory.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.runelite.http.api.ws; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * Adapts values whose runtime type may differ from their declaration type. This + * is necessary when a field's type is not the same type that GSON should create + * when deserializing that field. For example, consider these types: + *
   {@code
+ *   abstract class Shape {
+ *     int x;
+ *     int y;
+ *   }
+ *   class Circle extends Shape {
+ *     int radius;
+ *   }
+ *   class Rectangle extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Diamond extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Drawing {
+ *     Shape bottomShape;
+ *     Shape topShape;
+ *   }
+ * }
+ *

Without additional type information, the serialized JSON is ambiguous. Is + * the bottom shape in this drawing a rectangle or a diamond?

   {@code
+ *   {
+ *     "bottomShape": {
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * This class addresses this problem by adding type information to the + * serialized JSON and honoring that type information when the JSON is + * deserialized:
   {@code
+ *   {
+ *     "bottomShape": {
+ *       "type": "Diamond",
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "type": "Circle",
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * Both the type field name ({@code "type"}) and the type labels ({@code + * "Rectangle"}) are configurable. + * + *

Registering Types

+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field + * name to the {@link #of} factory method. If you don't supply an explicit type + * field name, {@code "type"} will be used.
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory
+ *       = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ * Next register all of your subtypes. Every subtype must be explicitly + * registered. This protects your application from injection attacks. If you + * don't supply an explicit type label, the type's simple name will be used. + *
   {@code
+ *   shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
+ *   shapeAdapter.registerSubtype(Circle.class, "Circle");
+ *   shapeAdapter.registerSubtype(Diamond.class, "Diamond");
+ * }
+ * Finally, register the type adapter factory in your application's GSON builder: + *
   {@code
+ *   Gson gson = new GsonBuilder()
+ *       .registerTypeAdapterFactory(shapeAdapterFactory)
+ *       .create();
+ * }
+ * Like {@code GsonBuilder}, this API supports chaining:
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
+ *       .registerSubtype(Rectangle.class)
+ *       .registerSubtype(Circle.class)
+ *       .registerSubtype(Diamond.class);
+ * }
+ */ +public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap>(); + private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); + + private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName) { + if (typeFieldName == null || baseType == null) { + throw new NullPointerException(); + } + this.baseType = baseType; + this.typeFieldName = typeFieldName; + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName); + } + + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as + * the type field name. + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory(baseType, "type"); + } + + /** + * Registers {@code type} identified by {@code label}. Labels are case + * sensitive. + * + * @throws IllegalArgumentException if either {@code type} or {@code label} + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { + if (type == null || label == null) { + throw new NullPointerException(); + } + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { + throw new IllegalArgumentException("types and labels must be unique"); + } + labelToSubtype.put(label, type); + subtypeToLabel.put(type, label); + return this; + } + + /** + * Registers {@code type} identified by its {@link Class#getSimpleName simple + * name}. Labels are case sensitive. + * + * @throws IllegalArgumentException if either {@code type} or its simple name + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } + + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() != baseType) { + return null; + } + + final Map> labelToDelegate + = new LinkedHashMap>(); + final Map, TypeAdapter> subtypeToDelegate + = new LinkedHashMap, TypeAdapter>(); + for (Map.Entry> entry : labelToSubtype.entrySet()) { + TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); + labelToDelegate.put(entry.getKey(), delegate); + subtypeToDelegate.put(entry.getValue(), delegate); + } + + return new TypeAdapter() { + @Override public R read(JsonReader in) throws IOException { + JsonElement jsonElement = Streams.parse(in); + JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + if (labelJsonElement == null) { + throw new JsonParseException("cannot deserialize " + baseType + + " because it does not define a field named " + typeFieldName); + } + String label = labelJsonElement.getAsString(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); + if (delegate == null) { + throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + + label + "; did you forget to register a subtype?"); + } + return delegate.fromJsonTree(jsonElement); + } + + @Override public void write(JsonWriter out, R value) throws IOException { + Class srcType = value.getClass(); + String label = subtypeToLabel.get(srcType); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); + if (delegate == null) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + "; did you forget to register a subtype?"); + } + JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); + if (jsonObject.has(typeFieldName)) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + " because it already defines a field named " + typeFieldName); + } + JsonObject clone = new JsonObject(); + clone.add(typeFieldName, new JsonPrimitive(label)); + for (Map.Entry e : jsonObject.entrySet()) { + clone.add(e.getKey(), e.getValue()); + } + Streams.write(clone, out); + } + }.nullSafe(); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/WebsocketGsonFactory.java b/http-api/src/main/java/net/runelite/http/api/ws/WebsocketGsonFactory.java new file mode 100644 index 0000000000..328d0aa1a7 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/WebsocketGsonFactory.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017, 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.ws; + +import com.google.gson.Gson; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.ws.messages.Handshake; +import net.runelite.http.api.ws.messages.LoginResponse; +import net.runelite.http.api.ws.messages.party.Join; +import net.runelite.http.api.ws.messages.party.Part; +import net.runelite.http.api.ws.messages.party.PartyChatMessage; +import net.runelite.http.api.ws.messages.party.UserJoin; +import net.runelite.http.api.ws.messages.party.UserPart; +import net.runelite.http.api.ws.messages.party.UserSync; + +public class WebsocketGsonFactory +{ + private static final Collection> MESSAGES; + + static + { + final List> messages = new ArrayList<>(); + messages.add(Handshake.class); + messages.add(LoginResponse.class); + messages.add(Join.class); + messages.add(Part.class); + messages.add(UserJoin.class); + messages.add(UserPart.class); + messages.add(UserSync.class); + messages.add(PartyChatMessage.class); + MESSAGES = messages; + } + + public static RuntimeTypeAdapterFactory factory(final Collection> messages) + { + final RuntimeTypeAdapterFactory factory = RuntimeTypeAdapterFactory.of(WebsocketMessage.class); + + for (Class message : MESSAGES) + { + factory.registerSubtype(message); + } + + for (Class message : messages) + { + factory.registerSubtype(message); + } + + return factory; + } + + public static Gson build(final RuntimeTypeAdapterFactory factory) + { + return RuneLiteAPI.GSON.newBuilder() + .registerTypeAdapterFactory(factory) + .create(); + } + + public static Gson build() + { + return build(factory(Collections.emptyList())); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/WebsocketMessage.java b/http-api/src/main/java/net/runelite/http/api/ws/WebsocketMessage.java new file mode 100644 index 0000000000..d732c110cd --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/WebsocketMessage.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 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.ws; + +public class WebsocketMessage +{ + protected boolean _party; + + public boolean isParty() + { + return _party; + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/Handshake.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/Handshake.java new file mode 100644 index 0000000000..5320c95ab5 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/Handshake.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 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.ws.messages; + +import java.util.UUID; +import lombok.Data; +import net.runelite.http.api.ws.WebsocketMessage; + +@Data +public class Handshake extends WebsocketMessage +{ + private UUID session; +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/LoginResponse.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/LoginResponse.java new file mode 100644 index 0000000000..8f22db8f80 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/LoginResponse.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, 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.ws.messages; + +import lombok.Data; +import net.runelite.http.api.ws.WebsocketMessage; + +/** + * Called after a successful login to the server + */ +@Data +public class LoginResponse extends WebsocketMessage +{ + private String username; +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java new file mode 100644 index 0000000000..21aed0f653 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.ws.messages.party; + +import java.util.UUID; +import lombok.EqualsAndHashCode; +import lombok.Value; +import net.runelite.http.api.ws.WebsocketMessage; + +@Value +@EqualsAndHashCode(callSuper = true) +public class Join extends WebsocketMessage +{ + private final UUID partyId; + private final String name; +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Part.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Part.java new file mode 100644 index 0000000000..e284ff0cf8 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Part.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.ws.messages.party; + +import net.runelite.http.api.ws.WebsocketMessage; + +public class Part extends WebsocketMessage +{ +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyChatMessage.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyChatMessage.java new file mode 100644 index 0000000000..480e2660c1 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyChatMessage.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019, Tomas Slusny + * 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.ws.messages.party; + +import lombok.Value; + +@Value +public class PartyChatMessage extends PartyMemberMessage +{ + private final String value; +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMemberMessage.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMemberMessage.java new file mode 100644 index 0000000000..9d5cab8545 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMemberMessage.java @@ -0,0 +1,12 @@ +package net.runelite.http.api.ws.messages.party; + +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public abstract class PartyMemberMessage extends PartyMessage +{ + private UUID memberId; +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMessage.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMessage.java new file mode 100644 index 0000000000..709457ed8c --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMessage.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.ws.messages.party; + +import net.runelite.http.api.ws.WebsocketMessage; + +public abstract class PartyMessage extends WebsocketMessage +{ + public PartyMessage() + { + _party = true; + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java new file mode 100644 index 0000000000..7f940e8060 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.ws.messages.party; + +import java.util.UUID; +import lombok.EqualsAndHashCode; +import lombok.Value; +import net.runelite.http.api.ws.WebsocketMessage; + +@Value +@EqualsAndHashCode(callSuper = true) +public class UserJoin extends WebsocketMessage +{ + private final UUID memberId; + private final UUID partyId; + private final String name; +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java new file mode 100644 index 0000000000..e80c6002bd --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.ws.messages.party; + +import java.util.UUID; +import lombok.EqualsAndHashCode; +import lombok.Value; +import net.runelite.http.api.ws.WebsocketMessage; + +@Value +@EqualsAndHashCode(callSuper = true) +public class UserPart extends WebsocketMessage +{ + private final UUID memberId; +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java new file mode 100644 index 0000000000..c95038c9fa --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.ws.messages.party; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper = true) +public class UserSync extends PartyMemberMessage +{ +} diff --git a/http-api/src/main/java/net/runelite/http/api/xtea/XteaKey.java b/http-api/src/main/java/net/runelite/http/api/xtea/XteaKey.java new file mode 100644 index 0000000000..bed2a017f1 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/xtea/XteaKey.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, 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.xtea; + +import lombok.Data; + +@Data +public class XteaKey +{ + private int region; + private int keys[]; +} diff --git a/http-api/src/main/java/net/runelite/http/api/xtea/XteaRequest.java b/http-api/src/main/java/net/runelite/http/api/xtea/XteaRequest.java new file mode 100644 index 0000000000..fdce607f59 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/xtea/XteaRequest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, 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.xtea; + +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +@Data +public class XteaRequest +{ + private int revision; + private List keys = new ArrayList<>(); + + public void addKey(XteaKey key) + { + keys.add(key); + } +} diff --git a/http-api/src/test/java/net/runelite/http/api/gson/ColorTypeAdapterTest.java b/http-api/src/test/java/net/runelite/http/api/gson/ColorTypeAdapterTest.java new file mode 100644 index 0000000000..0fe0b2226a --- /dev/null +++ b/http-api/src/test/java/net/runelite/http/api/gson/ColorTypeAdapterTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Abex + * 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.gson; + +import java.awt.Color; +import net.runelite.http.api.RuneLiteAPI; +import org.junit.Assert; +import org.junit.Test; + +public class ColorTypeAdapterTest +{ + @Test + public void test() + { + test("null", null, true); + test("{\"value\":-13347208,\"falpha\":0.0}", new Color(0x12345678, false), false); + test("{\"value\":305419896,\"falpha\":0.0}", new Color(0x12345678, true), false); + test("{\"value\":-1.4221317E7,\"falpha\":0.0}", new Color(0xFF26FFFB, true), false); + test("\"#FF345678\"", new Color(0x12345678, false), true); + test("\"#12345678\"", new Color(0x12345678, true), true); + test("\"#FF26FFFB\"", new Color(0xFF26FFFB, true), true); + } + + private void test(String json, Color object, boolean exactEncoding) + { + Color parsed = RuneLiteAPI.GSON.fromJson(json, Color.class); + Assert.assertEquals(object, parsed); + String serialized = RuneLiteAPI.GSON.toJson(object); + if (exactEncoding) + { + Assert.assertEquals(json, serialized); + } + Color roundTripped = RuneLiteAPI.GSON.fromJson(serialized, Color.class); + Assert.assertEquals(object, roundTripped); + } +} \ No newline at end of file diff --git a/http-api/src/test/java/net/runelite/http/api/gson/InstantTypeAdapterTest.java b/http-api/src/test/java/net/runelite/http/api/gson/InstantTypeAdapterTest.java new file mode 100644 index 0000000000..f9ed850843 --- /dev/null +++ b/http-api/src/test/java/net/runelite/http/api/gson/InstantTypeAdapterTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Abex + * 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.gson; + +import java.time.Instant; +import net.runelite.http.api.RuneLiteAPI; +import org.junit.Assert; +import org.junit.Test; + +public class InstantTypeAdapterTest +{ + @Test + public void test() + { + test("null", null, true); + test("{\"seconds\":1609538310,\"nanos\":291000000}", Instant.ofEpochSecond(1609538310, 291_000_000), false); + test("1609538310291", Instant.ofEpochSecond(1609538310, 291_000_000), true); + } + + private void test(String json, Instant object, boolean exactEncoding) + { + Instant parsed = RuneLiteAPI.GSON.fromJson(json, Instant.class); + Assert.assertEquals(object, parsed); + String serialized = RuneLiteAPI.GSON.toJson(object); + if (exactEncoding) + { + Assert.assertEquals(json, serialized); + } + Instant roundTripped = RuneLiteAPI.GSON.fromJson(serialized, Instant.class); + Assert.assertEquals(object, roundTripped); + } +} \ No newline at end of file diff --git a/http-service/pom.xml b/http-service/pom.xml new file mode 100644 index 0000000000..d983f9e9da --- /dev/null +++ b/http-service/pom.xml @@ -0,0 +1,269 @@ + + + + 4.0.0 + + + net.runelite + runelite-parent + 1.8.8-SNAPSHOT + + + Web Service + http-service + war + + + 1.5.6.RELEASE + nogit + false + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework + spring-jdbc + + + + org.projectlombok + lombok + provided + + + + net.runelite + http-api + ${project.version} + + + net.runelite + cache + ${project.version} + + + + org.mariadb.jdbc + mariadb-java-client + 2.2.3 + provided + + + org.sql2o + sql2o + 1.5.4 + + + com.google.guava + guava + + + com.github.scribejava + scribejava-apis + 4.1.0 + + + io.minio + minio + 3.0.6 + + + redis.clients + jedis + 2.10.0 + + + org.apache.commons + commons-pool2 + + + + + org.mongodb + mongodb-driver-sync + 3.10.2 + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.squareup.okhttp3 + mockwebserver + 3.14.9 + test + + + com.h2database + h2 + test + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + runelite-${project.version} + + + + src/main/resources + true + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.3.1 + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + false + + + + com.github.kongchen + swagger-maven-plugin + 3.1.8 + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + + + true + + net.runelite + + + https + + api.runelite.net + /runelite-${project.version} + + ${project.parent.name} HTTP API + ${project.version} + ${project.description} + + https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) + BSD 2-Clause "Simplified" + + + ${basedir}/src/main/templates/template.html.hbs + ${project.build.directory}/swagger-ui + ${project.build.directory}/site/api.html + true + json + + + + + + compile + + generate + + + + + + pl.project13.maven + git-commit-id-plugin + 2.2.6 + + + query-git-info + + revision + + + false + false + + true + + + git.commit.id.abbrev + git.dirty + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + @ + + false + + + + + diff --git a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java new file mode 100644 index 0000000000..4c425cb0cf --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2017, 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; + +import ch.qos.logback.classic.LoggerContext; +import com.google.common.base.Strings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import java.io.IOException; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.naming.NamingException; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.service.util.InstantConverter; +import okhttp3.Cache; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import org.slf4j.ILoggerFactory; +import org.slf4j.impl.StaticLoggerBinder; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; +import org.springframework.jndi.JndiTemplate; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.sql2o.Sql2o; +import org.sql2o.converters.Converter; +import org.sql2o.quirks.NoQuirks; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +@EnableScheduling +@Slf4j +public class SpringBootWebApplication extends SpringBootServletInitializer +{ + @Bean + protected ServletContextListener listener(OkHttpClient client) + { + return new ServletContextListener() + { + @Override + public void contextInitialized(ServletContextEvent sce) + { + log.info("RuneLite API started"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + // Destroy okhttp client + client.dispatcher().executorService().shutdown(); + client.connectionPool().evictAll(); + try + { + Cache cache = client.cache(); + if (cache != null) + { + cache.close(); + } + } + catch (IOException ex) + { + log.warn(null, ex); + } + + log.info("RuneLite API stopped"); + } + + }; + } + + @ConfigurationProperties(prefix = "datasource.runelite") + @Bean("dataSourceRuneLite") + public DataSourceProperties dataSourceProperties() + { + return new DataSourceProperties(); + } + + @ConfigurationProperties(prefix = "datasource.runelite-cache") + @Bean("dataSourceRuneLiteCache") + public DataSourceProperties dataSourcePropertiesCache() + { + return new DataSourceProperties(); + } + + @Bean(value = "runelite", destroyMethod = "") + public DataSource runeliteDataSource(@Qualifier("dataSourceRuneLite") DataSourceProperties dataSourceProperties) + { + return getDataSource(dataSourceProperties); + } + + @Bean(value = "runelite-cache", destroyMethod = "") + public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties) + { + return getDataSource(dataSourceProperties); + } + + @Bean("Runelite SQL2O") + public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource) + { + return createSql2oFromDataSource(dataSource); + } + + @Bean("Runelite Cache SQL2O") + public Sql2o cacheSql2o(@Qualifier("runelite-cache") DataSource dataSource) + { + return createSql2oFromDataSource(dataSource); + } + + @Bean(destroyMethod = "") + public MongoClient mongoClient(@Value("${mongo.host:}") String host, @Value("${mongo.jndiName:}") String jndiName) throws NamingException + { + if (!Strings.isNullOrEmpty(jndiName)) + { + JndiTemplate jndiTemplate = new JndiTemplate(); + return jndiTemplate.lookup(jndiName, MongoClient.class); + } + else if (!Strings.isNullOrEmpty(host)) + { + return MongoClients.create(host); + } + else + { + throw new RuntimeException("Either mongo.host or mongo.jndiName must be set"); + } + } + + private static DataSource getDataSource(DataSourceProperties dataSourceProperties) + { + if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName())) + { + // Use JNDI provided datasource, which is already configured with pooling + JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); + return dataSourceLookup.getDataSource(dataSourceProperties.getJndiName()); + } + else + { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + } + + private static Sql2o createSql2oFromDataSource(final DataSource dataSource) + { + final Map converters = new HashMap<>(); + converters.put(Instant.class, new InstantConverter()); + return new Sql2o(dataSource, new NoQuirks(converters)); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(SpringBootWebApplication.class); + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException + { + super.onStartup(servletContext); + ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory(); + if (loggerFactory instanceof LoggerContext) + { + LoggerContext loggerContext = (LoggerContext) loggerFactory; + loggerContext.setPackagingDataEnabled(false); + log.debug("Disabling logback packaging data"); + } + } + + @Bean + public OkHttpClient okHttpClient( + @Value("${runelite.version}") String version, + @Value("${runelite.commit}") String commit, + @Value("${runelite.dirty}") boolean dirty + ) + { + final String userAgent = "RuneLite/" + version + "-" + commit + (dirty ? "+" : ""); + return new OkHttpClient.Builder() + .pingInterval(30, TimeUnit.SECONDS) + .addNetworkInterceptor(chain -> + { + Request userAgentRequest = chain.request() + .newBuilder() + .header("User-Agent", userAgent) + .build(); + return chain.proceed(userAgentRequest); + }) + .build(); + } + + public static void main(String[] args) + { + SpringApplication.run(SpringBootWebApplication.class, args); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/SpringSchedulingConfigurer.java b/http-service/src/main/java/net/runelite/http/service/SpringSchedulingConfigurer.java new file mode 100644 index 0000000000..6af0895619 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/SpringSchedulingConfigurer.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021, 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; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +@Configuration +public class SpringSchedulingConfigurer implements SchedulingConfigurer +{ + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) + { + // this is from ScheduledTaskRegistrar.scheduleTasks() but modified to give the scheduler thread a + // recognizable name + ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder() + .setNameFormat("scheduler-%d") + .build() + ); + TaskScheduler scheduler = new ConcurrentTaskScheduler(scheduledExecutorService); + taskRegistrar.setTaskScheduler(scheduler); + } +} \ No newline at end of file diff --git a/http-service/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java b/http-service/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java new file mode 100644 index 0000000000..704e4f9cb2 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java @@ -0,0 +1,65 @@ +/* + * 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; + +import java.util.List; +import net.runelite.http.api.RuneLiteAPI; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@EnableWebMvc +public class SpringWebMvcConfigurer extends WebMvcConfigurerAdapter +{ + /** + * Configure .js as application/json to trick Cloudflare into caching json responses + */ + @Override + public void configureContentNegotiation(ContentNegotiationConfigurer configurer) + { + configurer.mediaType("js", MediaType.APPLICATION_JSON); + } + + /** + * Use GSON instead of Jackson for JSON serialization + * @param converters + */ + @Override + public void extendMessageConverters(List> converters) + { + // Could not figure out a better way to force GSON + converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance); + + GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter(); + gsonHttpMessageConverter.setGson(RuneLiteAPI.GSON); + converters.add(gsonHttpMessageConverter); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/AccountService.java b/http-service/src/main/java/net/runelite/http/service/account/AccountService.java new file mode 100644 index 0000000000..10a289b927 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/AccountService.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2017, 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.account; + +import com.github.scribejava.apis.GoogleApi20; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; +import com.google.gson.Gson; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.account.OAuthResponse; +import net.runelite.http.api.ws.WebsocketGsonFactory; +import net.runelite.http.api.ws.WebsocketMessage; +import net.runelite.http.api.ws.messages.LoginResponse; +import net.runelite.http.service.account.beans.SessionEntry; +import net.runelite.http.service.account.beans.UserEntry; +import net.runelite.http.service.util.redis.RedisPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.sql2o.Connection; +import org.sql2o.Sql2o; +import org.sql2o.Sql2oException; +import redis.clients.jedis.Jedis; + +@RestController +@RequestMapping("/account") +public class AccountService +{ + private static final Logger logger = LoggerFactory.getLogger(AccountService.class); + + private static final String CREATE_SESSIONS = "CREATE TABLE IF NOT EXISTS `sessions` (\n" + + " `user` int(11) NOT NULL PRIMARY KEY,\n" + + " `uuid` varchar(36) NOT NULL,\n" + + " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" + + " `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" + + " UNIQUE KEY `uuid` (`uuid`),\n" + + " KEY `user` (`user`)\n" + + ") ENGINE=InnoDB"; + + private static final String CREATE_USERS = "CREATE TABLE IF NOT EXISTS `users` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `username` tinytext NOT NULL,\n" + + " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" + + " PRIMARY KEY (`id`),\n" + + " UNIQUE KEY `username` (`username`(64))\n" + + ") ENGINE=InnoDB"; + + private static final String SESSIONS_FK = "ALTER TABLE `sessions`\n" + + " ADD CONSTRAINT `id` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;"; + + private static final String SCOPE = "https://www.googleapis.com/auth/userinfo.email"; + private static final String USERINFO = "https://www.googleapis.com/oauth2/v2/userinfo"; + private static final String RL_REDIR = "https://runelite.net/logged-in"; + + private final Gson gson = RuneLiteAPI.GSON; + private final Gson websocketGson = WebsocketGsonFactory.build(); + + private final Sql2o sql2o; + private final String oauthClientId; + private final String oauthClientSecret; + private final String oauthCallback; + private final String runeliteVersion; + private final AuthFilter auth; + private final RedisPool jedisPool; + + @Autowired + public AccountService( + @Qualifier("Runelite SQL2O") Sql2o sql2o, + @Value("${oauth.client-id}") String oauthClientId, + @Value("${oauth.client-secret}") String oauthClientSecret, + @Value("${oauth.callback}") String oauthCallback, + @Value("${runelite.version}") String runeliteVersion, + AuthFilter auth, + RedisPool jedisPool + ) + { + this.sql2o = sql2o; + this.oauthClientId = oauthClientId; + this.oauthClientSecret = oauthClientSecret; + this.oauthCallback = oauthCallback; + this.runeliteVersion = runeliteVersion; + this.auth = auth; + this.jedisPool = jedisPool; + + try (Connection con = sql2o.open()) + { + con.createQuery(CREATE_SESSIONS) + .executeUpdate(); + + con.createQuery(CREATE_USERS) + .executeUpdate(); + + try + { + con.createQuery(SESSIONS_FK) + .executeUpdate(); + } + catch (Sql2oException ex) + { + // Ignore, happens when index already exists + } + } + } + + @GetMapping("/login") + public OAuthResponse login(@RequestParam UUID uuid) + { + State state = new State(); + state.setUuid(uuid); + state.setApiVersion(runeliteVersion); + + OAuth20Service service = new ServiceBuilder() + .apiKey(oauthClientId) + .apiSecret(oauthClientSecret) + .scope(SCOPE) + .callback(oauthCallback) + .state(gson.toJson(state)) + .build(GoogleApi20.instance()); + + final Map additionalParams = new HashMap<>(); + additionalParams.put("prompt", "select_account"); + + String authorizationUrl = service.getAuthorizationUrl(additionalParams); + + OAuthResponse lr = new OAuthResponse(); + lr.setOauthUrl(authorizationUrl); + lr.setUid(uuid); + + return lr; + } + + @GetMapping("/callback") + public Object callback( + HttpServletRequest request, + HttpServletResponse response, + @RequestParam(required = false) String error, + @RequestParam String code, + @RequestParam("state") String stateStr + ) throws InterruptedException, ExecutionException, IOException + { + if (error != null) + { + logger.info("Error in oauth callback: {}", error); + return null; + } + + State state = gson.fromJson(stateStr, State.class); + + logger.info("Got authorization code {} for uuid {}", code, state.getUuid()); + + OAuth20Service service = new ServiceBuilder() + .apiKey(oauthClientId) + .apiSecret(oauthClientSecret) + .scope(SCOPE) + .callback(oauthCallback) + .state(gson.toJson(state)) + .build(GoogleApi20.instance()); + + OAuth2AccessToken accessToken = service.getAccessToken(code); + + // Access user info + OAuthRequest orequest = new OAuthRequest(Verb.GET, USERINFO); + service.signRequest(accessToken, orequest); + + Response oresponse = service.execute(orequest); + + if (oresponse.getCode() / 100 != 2) + { + // Could be a forged result + return null; + } + + UserInfo userInfo = gson.fromJson(oresponse.getBody(), UserInfo.class); + + logger.info("Got user info: {}", userInfo); + + try (Connection con = sql2o.open()) + { + con.createQuery("insert ignore into users (username) values (:username)") + .addParameter("username", userInfo.getEmail()) + .executeUpdate(); + + UserEntry user = con.createQuery("select id from users where username = :username") + .addParameter("username", userInfo.getEmail()) + .executeAndFetchFirst(UserEntry.class); + + if (user == null) + { + logger.warn("Unable to find newly created user session"); + return null; // that's weird + } + + // insert session + con.createQuery("insert ignore into sessions (user, uuid) values (:user, :uuid)") + .addParameter("user", user.getId()) + .addParameter("uuid", state.getUuid().toString()) + .executeUpdate(); + + logger.info("Created session for user {}", userInfo.getEmail()); + } + + response.sendRedirect(RL_REDIR); + + notifySession(state.getUuid(), userInfo.getEmail()); + + return ""; + } + + private void notifySession(UUID uuid, String username) + { + LoginResponse response = new LoginResponse(); + response.setUsername(username); + + try (Jedis jedis = jedisPool.getResource()) + { + jedis.publish("session." + uuid, websocketGson.toJson(response, WebsocketMessage.class)); + } + } + + @GetMapping("/logout") + public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException + { + SessionEntry session = auth.handle(request, response); + + if (session == null) + { + return; + } + + auth.invalidate(session.getUuid()); + + try (Connection con = sql2o.open()) + { + con.createQuery("delete from sessions where uuid = :uuid") + .addParameter("uuid", session.getUuid().toString()) + .executeUpdate(); + } + } + + @GetMapping("/session-check") + public void sessionCheck(HttpServletRequest request, HttpServletResponse response) throws IOException + { + auth.handle(request, response); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java b/http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java new file mode 100644 index 0000000000..d283e71c5c --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/AuthFilter.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017, 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.account; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalNotification; +import java.io.IOException; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.service.account.beans.SessionEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Sql2o; + +@Service +public class AuthFilter +{ + private final Sql2o sql2o; + + private final Cache sessionCache = CacheBuilder.newBuilder() + .maximumSize(10000L) + .expireAfterAccess(30, TimeUnit.MINUTES) + .removalListener(this::removalListener) + .build(); + + @Autowired + public AuthFilter(@Qualifier("Runelite SQL2O") Sql2o sql2o) + { + this.sql2o = sql2o; + } + + public SessionEntry handle(HttpServletRequest request, HttpServletResponse response) throws IOException + { + String runeliteAuth = request.getHeader(RuneLiteAPI.RUNELITE_AUTH); + if (runeliteAuth == null) + { + response.sendError(401, "Access denied"); + return null; + } + + UUID uuid = UUID.fromString(runeliteAuth); + SessionEntry sessionEntry = sessionCache.getIfPresent(uuid); + if (sessionEntry != null) + { + return sessionEntry; + } + + try (Connection con = sql2o.open()) + { + sessionEntry = con.createQuery("select user, uuid, created, last_used as lastUsed from sessions where uuid = :uuid") + .addParameter("uuid", uuid.toString()) + .executeAndFetchFirst(SessionEntry.class); + } + + if (sessionEntry == null) + { + response.sendError(401, "Access denied"); + return null; + } + + sessionCache.put(uuid, sessionEntry); + + return sessionEntry; + } + + private void removalListener(RemovalNotification notification) + { + UUID uuid = notification.getKey(); + Instant now = Instant.now(); + + try (Connection con = sql2o.open()) + { + con.createQuery("update sessions set last_used = :last_used where uuid = :uuid") + .addParameter("last_used", Timestamp.from(now)) + .addParameter("uuid", uuid.toString()) + .executeUpdate(); + } + } + + public void invalidate(UUID uuid) + { + // If we ever run multiple services, may need to publish something here to invalidate... + sessionCache.invalidate(uuid); + } + +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/State.java b/http-service/src/main/java/net/runelite/http/service/account/State.java new file mode 100644 index 0000000000..1412efa11a --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/State.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 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.account; + +import java.util.UUID; +import lombok.Data; + +@Data +public class State +{ + private UUID uuid; + private String apiVersion; +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/UserInfo.java b/http-service/src/main/java/net/runelite/http/service/account/UserInfo.java new file mode 100644 index 0000000000..a1cde03f79 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/UserInfo.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, 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.account; + +public class UserInfo +{ + private String email; + + @Override + public String toString() + { + return "UserInfo{" + "email=" + email + '}'; + } + + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/beans/SessionEntry.java b/http-service/src/main/java/net/runelite/http/service/account/beans/SessionEntry.java new file mode 100644 index 0000000000..ded67c7a45 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/beans/SessionEntry.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017, 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.account.beans; + +import java.time.Instant; +import java.util.UUID; + +public class SessionEntry +{ + private int user; + private UUID uuid; + private Instant created; + private Instant lastUsed; + + public int getUser() + { + return user; + } + + public void setUser(int user) + { + this.user = user; + } + + public UUID getUuid() + { + return uuid; + } + + public void setUuid(UUID uuid) + { + this.uuid = uuid; + } + + public Instant getCreated() + { + return created; + } + + public void setCreated(Instant created) + { + this.created = created; + } + + public Instant getLastUsed() + { + return lastUsed; + } + + public void setLastUsed(Instant lastUsed) + { + this.lastUsed = lastUsed; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/account/beans/UserEntry.java b/http-service/src/main/java/net/runelite/http/service/account/beans/UserEntry.java new file mode 100644 index 0000000000..83dd4152ac --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/account/beans/UserEntry.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, 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.account.beans; + +public class UserEntry +{ + private int id; + private String username; + + @Override + public String toString() + { + return "UserEntry{" + "id=" + id + ", username=" + username + '}'; + } + + public int getId() + { + return id; + } + + public void setId(int id) + { + this.id = id; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheDAO.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheDAO.java new file mode 100644 index 0000000000..08282ca38e --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheDAO.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2017, 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.cache; + +import java.util.List; +import net.runelite.cache.IndexType; +import net.runelite.http.service.cache.beans.ArchiveEntry; +import net.runelite.http.service.cache.beans.CacheEntry; +import net.runelite.http.service.cache.beans.FileEntry; +import net.runelite.http.service.cache.beans.IndexEntry; +import org.sql2o.Connection; +import org.sql2o.Query; +import org.sql2o.ResultSetIterable; + +class CacheDAO +{ + public List listCaches(Connection con) + { + return con.createQuery("select id, revision, date from cache") + .executeAndFetch(CacheEntry.class); + } + + public CacheEntry findMostRecent(Connection con) + { + return con.createQuery("select id, revision, date from cache order by revision desc, date desc limit 1") + .executeAndFetchFirst(CacheEntry.class); + } + + public List findIndexesForCache(Connection con, CacheEntry cache) + { + return con.createQuery("select id, indexId, crc, revision from `index` where cache = :cache") + .addParameter("cache", cache.getId()) + .executeAndFetch(IndexEntry.class); + } + + public IndexEntry findIndexForCache(Connection con, CacheEntry cache, int indexId) + { + return con.createQuery("select id, indexId, crc, revision from `index` " + + "where cache = :id " + + "and indexId = :indexId") + .addParameter("id", cache.getId()) + .addParameter("indexId", indexId) + .executeAndFetchFirst(IndexEntry.class); + } + + public ResultSetIterable findArchivesForIndex(Connection con, IndexEntry indexEntry) + { + return con.createQuery("select archive.id, archive.archiveId, archive.nameHash," + + " archive.crc, archive.revision, archive.hash from index_archive " + + "join archive on index_archive.archive = archive.id " + + "where index_archive.index = :id") + .addParameter("id", indexEntry.getId()) + .executeAndFetchLazy(ArchiveEntry.class); + } + + public ArchiveEntry findArchiveForIndex(Connection con, IndexEntry indexEntry, int archiveId) + { + return con.createQuery("select archive.id, archive.archiveId, archive.nameHash," + + " archive.crc, archive.revision, archive.hash from index_archive " + + "join archive on index_archive.archive = archive.id " + + "where index_archive.index = :id " + + "and archive.archiveId = :archiveId") + .addParameter("id", indexEntry.getId()) + .addParameter("archiveId", archiveId) + .executeAndFetchFirst(ArchiveEntry.class); + } + + public ArchiveEntry findArchiveByName(Connection con, CacheEntry cache, IndexType index, int nameHash) + { + return con.createQuery("select archive.id, archive.archiveId, archive.nameHash," + + " archive.crc, archive.revision, archive.hash from archive " + + "join index_archive on index_archive.archive = archive.id " + + "join `index` on index.id = index_archive.index " + + "where index.cache = :cacheId " + + "and index.indexId = :indexId " + + "and archive.nameHash = :nameHash " + + "limit 1") + .addParameter("cacheId", cache.getId()) + .addParameter("indexId", index.getNumber()) + .addParameter("nameHash", nameHash) + .executeAndFetchFirst(ArchiveEntry.class); + } + + public ResultSetIterable findFilesForArchive(Connection con, ArchiveEntry archiveEntry) + { + Query findFilesForArchive = con.createQuery("select id, fileId, nameHash from file " + + "where archive = :archive"); + + return findFilesForArchive + .addParameter("archive", archiveEntry.getId()) + .executeAndFetchLazy(FileEntry.class); + } + + public CacheEntry findCache(Connection con, int cacheId) + { + return con.createQuery("select id, revision, date from cache " + + "where id = :cacheId") + .addParameter("cacheId", cacheId) + .executeAndFetchFirst(CacheEntry.class); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java new file mode 100644 index 0000000000..c0b767a68c --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2017-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.cache; + +import com.google.common.collect.Iterables; +import com.google.common.io.BaseEncoding; +import com.google.common.io.ByteStreams; +import io.minio.MinioClient; +import io.minio.errors.ErrorResponseException; +import io.minio.errors.InsufficientDataException; +import io.minio.errors.InternalException; +import io.minio.errors.InvalidArgumentException; +import io.minio.errors.InvalidBucketNameException; +import io.minio.errors.InvalidEndpointException; +import io.minio.errors.InvalidPortException; +import io.minio.errors.NoResponseException; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import net.runelite.cache.ConfigType; +import net.runelite.cache.IndexType; +import net.runelite.cache.definitions.ItemDefinition; +import net.runelite.cache.definitions.loaders.ItemLoader; +import net.runelite.cache.fs.ArchiveFiles; +import net.runelite.cache.fs.Container; +import net.runelite.cache.fs.FSFile; +import net.runelite.http.service.cache.beans.ArchiveEntry; +import net.runelite.http.service.cache.beans.CacheEntry; +import net.runelite.http.service.cache.beans.FileEntry; +import net.runelite.http.service.cache.beans.IndexEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.ResultSetIterable; +import org.sql2o.Sql2o; +import org.xmlpull.v1.XmlPullParserException; + +@Service +@Slf4j +public class CacheService +{ + @Autowired + @Qualifier("Runelite Cache SQL2O") + private Sql2o sql2o; + + @Value("${minio.bucket}") + private String minioBucket; + + private final MinioClient minioClient; + + @Autowired + public CacheService( + @Value("${minio.endpoint}") String minioEndpoint, + @Value("${minio.accesskey}") String accessKey, + @Value("${minio.secretkey}") String secretKey + ) throws InvalidEndpointException, InvalidPortException + { + this.minioClient = new MinioClient(minioEndpoint, accessKey, secretKey); + } + + @Bean + public MinioClient minioClient() + { + return minioClient; + } + + /** + * retrieve archive from storage + * + * @param archiveEntry + * @return + */ + public byte[] getArchive(ArchiveEntry archiveEntry) + { + String hashStr = BaseEncoding.base16().encode(archiveEntry.getHash()); + String path = new StringBuilder() + .append(hashStr, 0, 2) + .append('/') + .append(hashStr.substring(2)) + .toString(); + + try (InputStream in = minioClient.getObject(minioBucket, path)) + { + return ByteStreams.toByteArray(in); + } + catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException + | IOException | InvalidKeyException | NoResponseException | XmlPullParserException + | ErrorResponseException | InternalException | InvalidArgumentException ex) + { + log.warn(null, ex); + return null; + } + } + + public ArchiveFiles getArchiveFiles(ArchiveEntry archiveEntry) throws IOException + { + CacheDAO cacheDao = new CacheDAO(); + + try (Connection con = sql2o.open(); + ResultSetIterable files = cacheDao.findFilesForArchive(con, archiveEntry)) + { + byte[] archiveData = getArchive(archiveEntry); + + if (archiveData == null) + { + return null; + } + + Container result = Container.decompress(archiveData, null); + if (result == null) + { + return null; + } + + byte[] decompressedData = result.data; + + ArchiveFiles archiveFiles = new ArchiveFiles(); + for (FileEntry fileEntry : files) + { + FSFile file = new FSFile(fileEntry.getFileId()); + archiveFiles.addFile(file); + file.setNameHash(fileEntry.getNameHash()); + } + archiveFiles.loadContents(decompressedData); + return archiveFiles; + } + } + + public List listCaches() + { + try (Connection con = sql2o.open()) + { + CacheDAO cacheDao = new CacheDAO(); + return cacheDao.listCaches(con); + } + } + + public CacheEntry findCache(int cacheId) + { + try (Connection con = sql2o.open()) + { + CacheDAO cacheDao = new CacheDAO(); + return cacheDao.findCache(con, cacheId); + } + } + + public CacheEntry findMostRecent() + { + try (Connection con = sql2o.open()) + { + CacheDAO cacheDao = new CacheDAO(); + return cacheDao.findMostRecent(con); + } + } + + public List findIndexesForCache(CacheEntry cacheEntry) + { + try (Connection con = sql2o.open()) + { + CacheDAO cacheDao = new CacheDAO(); + return cacheDao.findIndexesForCache(con, cacheEntry); + } + } + + public IndexEntry findIndexForCache(CacheEntry cahceEntry, int indexId) + { + try (Connection con = sql2o.open()) + { + CacheDAO cacheDao = new CacheDAO(); + return cacheDao.findIndexForCache(con, cahceEntry, indexId); + } + } + + public List findArchivesForIndex(IndexEntry indexEntry) + { + try (Connection con = sql2o.open()) + { + CacheDAO cacheDao = new CacheDAO(); + ResultSetIterable archiveEntries = cacheDao.findArchivesForIndex(con, indexEntry); + List archives = new ArrayList<>(); + Iterables.addAll(archives, archiveEntries); + return archives; + } + } + + public ArchiveEntry findArchiveForIndex(IndexEntry indexEntry, int archiveId) + { + try (Connection con = sql2o.open()) + { + CacheDAO cacheDao = new CacheDAO(); + return cacheDao.findArchiveForIndex(con, indexEntry, archiveId); + } + } + + public ArchiveEntry findArchiveForTypeAndName(CacheEntry cache, IndexType index, int nameHash) + { + try (Connection con = sql2o.open()) + { + CacheDAO cacheDao = new CacheDAO(); + return cacheDao.findArchiveByName(con, cache, index, nameHash); + } + } + + public List getItems() throws IOException + { + CacheEntry cache = findMostRecent(); + if (cache == null) + { + return Collections.emptyList(); + } + + IndexEntry indexEntry = findIndexForCache(cache, IndexType.CONFIGS.getNumber()); + ArchiveEntry archiveEntry = findArchiveForIndex(indexEntry, ConfigType.ITEM.getId()); + ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry); + final ItemLoader itemLoader = new ItemLoader(); + final List result = new ArrayList<>(archiveFiles.getFiles().size()); + for (FSFile file : archiveFiles.getFiles()) + { + ItemDefinition itemDef = itemLoader.load(file.getFileId(), file.getContents()); + result.add(itemDef); + } + return result; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/cache/beans/ArchiveEntry.java b/http-service/src/main/java/net/runelite/http/service/cache/beans/ArchiveEntry.java new file mode 100644 index 0000000000..acda96e77f --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/cache/beans/ArchiveEntry.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 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.cache.beans; + +import lombok.Data; + +@Data +public class ArchiveEntry +{ + private int id; + private int archiveId; + private int nameHash; + private int crc; + private int revision; + private byte[] hash; +} diff --git a/http-service/src/main/java/net/runelite/http/service/cache/beans/CacheEntry.java b/http-service/src/main/java/net/runelite/http/service/cache/beans/CacheEntry.java new file mode 100644 index 0000000000..231ad7c655 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/cache/beans/CacheEntry.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 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.cache.beans; + +import java.time.Instant; +import lombok.Data; + +@Data +public class CacheEntry +{ + private int id; + private int revision; + private Instant date; +} diff --git a/http-service/src/main/java/net/runelite/http/service/cache/beans/FileEntry.java b/http-service/src/main/java/net/runelite/http/service/cache/beans/FileEntry.java new file mode 100644 index 0000000000..c5f35a4cc3 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/cache/beans/FileEntry.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 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.cache.beans; + +import lombok.Data; + +@Data +public class FileEntry +{ + private int id; + private int archiveId; + private int fileId; + private int nameHash; +} diff --git a/http-service/src/main/java/net/runelite/http/service/cache/beans/IndexEntry.java b/http-service/src/main/java/net/runelite/http/service/cache/beans/IndexEntry.java new file mode 100644 index 0000000000..8d60927c71 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/cache/beans/IndexEntry.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 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.cache.beans; + +import lombok.Data; + +@Data +public class IndexEntry +{ + private int id; + private int indexId; + private int crc; + private int revision; +} 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..954c90e94b --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/chat/ChatController.java @@ -0,0 +1,274 @@ +/* + * 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 com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.runelite.http.api.chat.Duels; +import net.runelite.http.api.chat.LayoutRoom; +import net.runelite.http.api.chat.Task; +import net.runelite.http.service.util.exception.NotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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("/chat") +public class ChatController +{ + private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]"); + private static final int STRING_MAX_LENGTH = 50; + private static final int MAX_LAYOUT_ROOMS = 16; + private static final int MAX_PETS = 256; + + private final Cache killCountCache = CacheBuilder.newBuilder() + .expireAfterWrite(2, TimeUnit.MINUTES) + .maximumSize(128L) + .build(); + + @Autowired + private ChatService chatService; + + @PostMapping("/kc") + public void submitKc(@RequestParam String name, @RequestParam String boss, @RequestParam int kc) + { + if (kc <= 0) + { + return; + } + + chatService.setKc(name, boss, kc); + killCountCache.put(new KillCountKey(name, boss), kc); + } + + @GetMapping("/kc") + public int getKc(@RequestParam String name, @RequestParam String boss) + { + Integer kc = killCountCache.getIfPresent(new KillCountKey(name, boss)); + if (kc == null) + { + kc = chatService.getKc(name, boss); + if (kc != null) + { + killCountCache.put(new KillCountKey(name, boss), kc); + } + } + + if (kc == null) + { + throw new NotFoundException(); + } + return kc; + } + + @PostMapping("/qp") + public void submitQp(@RequestParam String name, @RequestParam int qp) + { + if (qp < 0) + { + return; + } + + chatService.setQp(name, qp); + } + + @GetMapping("/qp") + public int getQp(@RequestParam String name) + { + Integer kc = chatService.getQp(name); + if (kc == null) + { + throw new NotFoundException(); + } + return kc; + } + + @PostMapping("/gc") + public void submitGc(@RequestParam String name, @RequestParam int gc) + { + if (gc < 0) + { + return; + } + + chatService.setGc(name, gc); + } + + @GetMapping("/gc") + public int getKc(@RequestParam String name) + { + Integer gc = chatService.getGc(name); + if (gc == null) + { + throw new NotFoundException(); + } + return gc; + } + + @PostMapping("/task") + public void submitTask(@RequestParam String name, @RequestParam("task") String taskName, @RequestParam int amount, + @RequestParam int initialAmount, @RequestParam String location) + { + Matcher mTask = STRING_VALIDATION.matcher(taskName); + Matcher mLocation = STRING_VALIDATION.matcher(location); + if (mTask.find() || taskName.length() > STRING_MAX_LENGTH || + mLocation.find() || location.length() > STRING_MAX_LENGTH) + { + return; + } + + Task task = new Task(); + task.setTask(taskName); + task.setAmount(amount); + task.setInitialAmount(initialAmount); + task.setLocation(location); + + chatService.setTask(name, task); + } + + @GetMapping("/task") + public ResponseEntity getTask(@RequestParam String name) + { + Task task = chatService.getTask(name); + if (task == null) + { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .build(); + } + + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(2, TimeUnit.MINUTES).cachePublic()) + .body(task); + } + + @PostMapping("/pb") + public void submitPb(@RequestParam String name, @RequestParam String boss, @RequestParam double pb) + { + if (pb < 0) + { + return; + } + + chatService.setPb(name, boss, pb); + } + + @GetMapping("/pb") + public double getPb(@RequestParam String name, @RequestParam String boss) + { + Double pb = chatService.getPb(name, boss); + if (pb == null) + { + throw new NotFoundException(); + } + return pb; + } + + @PostMapping("/duels") + public void submitDuels(@RequestParam String name, @RequestParam int wins, + @RequestParam int losses, + @RequestParam int winningStreak, @RequestParam int losingStreak) + { + if (wins < 0 || losses < 0 || winningStreak < 0 || losingStreak < 0) + { + return; + } + + Duels duels = new Duels(); + duels.setWins(wins); + duels.setLosses(losses); + duels.setWinningStreak(winningStreak); + duels.setLosingStreak(losingStreak); + + chatService.setDuels(name, duels); + } + + @GetMapping("/duels") + public Duels getDuels(@RequestParam String name) + { + Duels duels = chatService.getDuels(name); + if (duels == null) + { + throw new NotFoundException(); + } + return duels; + } + + @PostMapping("/layout") + public void submitLayout(@RequestParam String name, @RequestBody LayoutRoom[] rooms) + { + if (rooms.length > MAX_LAYOUT_ROOMS) + { + return; + } + + chatService.setLayout(name, rooms); + } + + @GetMapping("/layout") + public LayoutRoom[] getLayout(@RequestParam String name) + { + LayoutRoom[] layout = chatService.getLayout(name); + + if (layout == null) + { + throw new NotFoundException(); + } + + return layout; + } + + @PostMapping("/pets") + public void submitPetList(@RequestParam String name, @RequestBody int[] petList) + { + if (petList.length == 0 || petList.length > MAX_PETS) + { + return; + } + + chatService.setPetList(name, petList); + } + + @GetMapping("/pets") + public int[] getPetList(@RequestParam String name) + { + int[] petList = chatService.getPetList(name); + if (petList == null) + { + throw new NotFoundException(); + } + + return petList; + } +} 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..7748728aab --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/chat/ChatService.java @@ -0,0 +1,263 @@ +/* + * 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 com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableMap; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.runelite.http.api.chat.Duels; +import net.runelite.http.api.chat.LayoutRoom; +import net.runelite.http.api.chat.Task; +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; + + @Autowired + public ChatService(RedisPool jedisPool) + { + this.jedisPool = jedisPool; + } + + public Integer getKc(String name, String boss) + { + String value; + try (Jedis jedis = jedisPool.getResource()) + { + value = jedis.get("kc." + name + "." + boss); + } + return value == null ? null : Integer.parseInt(value); + } + + public void setKc(String name, String boss, int kc) + { + try (Jedis jedis = jedisPool.getResource()) + { + jedis.setex("kc." + name + "." + boss, (int) EXPIRE.getSeconds(), Integer.toString(kc)); + } + } + + public Integer getQp(String name) + { + String value; + try (Jedis jedis = jedisPool.getResource()) + { + value = jedis.get("qp." + name); + } + return value == null ? null : Integer.parseInt(value); + } + + public void setQp(String name, int qp) + { + try (Jedis jedis = jedisPool.getResource()) + { + jedis.setex("qp." + name, (int) EXPIRE.getSeconds(), Integer.toString(qp)); + } + } + + public Task getTask(String name) + { + Map map; + + try (Jedis jedis = jedisPool.getResource()) + { + map = jedis.hgetAll("task." + name); + } + + if (map.isEmpty()) + { + return null; + } + + Task task = new Task(); + task.setTask(map.get("task")); + task.setAmount(Integer.parseInt(map.get("amount"))); + task.setInitialAmount(Integer.parseInt(map.get("initialAmount"))); + task.setLocation(map.get("location")); + return task; + } + + public void setTask(String name, Task task) + { + Map taskMap = ImmutableMap.builderWithExpectedSize(4) + .put("task", task.getTask()) + .put("amount", Integer.toString(task.getAmount())) + .put("initialAmount", Integer.toString(task.getInitialAmount())) + .put("location", task.getLocation()) + .build(); + + String key = "task." + name; + + try (Jedis jedis = jedisPool.getResource()) + { + jedis.hmset(key, taskMap); + jedis.expire(key, (int) EXPIRE.getSeconds()); + } + } + + public Double getPb(String name, String boss) + { + String value; + try (Jedis jedis = jedisPool.getResource()) + { + value = jedis.get("pb." + boss + "." + name); + } + return value == null ? null : Double.parseDouble(value); + } + + public void setPb(String name, String boss, double pb) + { + try (Jedis jedis = jedisPool.getResource()) + { + jedis.setex("pb." + boss + "." + name, (int) EXPIRE.getSeconds(), Double.toString(pb)); + } + } + + public Integer getGc(String name) + { + String value; + try (Jedis jedis = jedisPool.getResource()) + { + value = jedis.get("gc." + name); + } + return value == null ? null : Integer.parseInt(value); + } + + public void setGc(String name, int gc) + { + try (Jedis jedis = jedisPool.getResource()) + { + jedis.setex("gc." + name, (int) EXPIRE.getSeconds(), Integer.toString(gc)); + } + } + + public Duels getDuels(String name) + { + Map map; + + try (Jedis jedis = jedisPool.getResource()) + { + map = jedis.hgetAll("duels." + name); + } + + if (map.isEmpty()) + { + return null; + } + + Duels duels = new Duels(); + duels.setWins(Integer.parseInt(map.get("wins"))); + duels.setLosses(Integer.parseInt(map.get("losses"))); + duels.setWinningStreak(Integer.parseInt(map.get("winningStreak"))); + duels.setLosingStreak(Integer.parseInt(map.get("losingStreak"))); + return duels; + } + + public void setDuels(String name, Duels duels) + { + Map duelsMap = ImmutableMap.builderWithExpectedSize(4) + .put("wins", Integer.toString(duels.getWins())) + .put("losses", Integer.toString(duels.getLosses())) + .put("winningStreak", Integer.toString(duels.getWinningStreak())) + .put("losingStreak", Integer.toString(duels.getLosingStreak())) + .build(); + + String key = "duels." + name; + + try (Jedis jedis = jedisPool.getResource()) + { + jedis.hmset(key, duelsMap); + jedis.expire(key, (int) EXPIRE.getSeconds()); + } + } + + public LayoutRoom[] getLayout(String name) + { + String layout; + try (Jedis jedis = jedisPool.getResource()) + { + layout = jedis.get("layout." + name); + } + + if (layout == null) + { + return null; + } + + List roomList = Splitter.on(' ').splitToList(layout); + return roomList.stream() + .map(LayoutRoom::valueOf) + .toArray(LayoutRoom[]::new); + } + + public void setLayout(String name, LayoutRoom[] rooms) + { + try (Jedis jedis = jedisPool.getResource()) + { + jedis.setex("layout." + name, (int) EXPIRE.getSeconds(), Joiner.on(' ').join(rooms)); + } + } + + public int[] getPetList(String name) + { + Set pets; + try (Jedis jedis = jedisPool.getResource()) + { + pets = jedis.smembers("pets." + name); + } + + if (pets.isEmpty()) + { + return null; + } + + return pets.stream() + .mapToInt(Integer::parseInt) + .toArray(); + } + + public void setPetList(String name, int[] petList) + { + String[] pets = Arrays.stream(petList).mapToObj(Integer::toString).toArray(String[]::new); + String key = "pets." + name; + try (Jedis jedis = jedisPool.getResource()) + { + jedis.sadd(key, pets); + jedis.expire(key, (int) EXPIRE.getSeconds()); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/chat/KillCountKey.java b/http-service/src/main/java/net/runelite/http/service/chat/KillCountKey.java new file mode 100644 index 0000000000..07ca775dad --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/chat/KillCountKey.java @@ -0,0 +1,34 @@ +/* + * 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 lombok.Value; + +@Value +class KillCountKey +{ + private String username; + private String boss; +} diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java new file mode 100644 index 0000000000..c247ecc0bc --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java @@ -0,0 +1,134 @@ +/* + * 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.config; + +import java.io.IOException; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.config.Configuration; +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.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/config") +public class ConfigController +{ + private final ConfigService configService; + private final AuthFilter authFilter; + + @Autowired + public ConfigController(ConfigService configService, AuthFilter authFilter) + { + this.configService = configService; + this.authFilter = authFilter; + } + + @GetMapping + public Configuration get(HttpServletRequest request, HttpServletResponse response) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return null; + } + + return configService.get(session.getUser()); + } + + @PatchMapping + public List patch( + HttpServletRequest request, + HttpServletResponse response, + @RequestBody Configuration changes + ) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + if (session == null) + { + return null; + } + + List failures = configService.patch(session.getUser(), changes); + if (failures.size() != 0) + { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return failures; + } + + return null; + } + + @RequestMapping(path = "/{key:.+}", method = PUT) + public void setKey( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable String key, + @RequestBody(required = false) String value + ) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + if (!configService.setKey(session.getUser(), key, value)) + { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + + @RequestMapping(path = "/{key:.+}", method = DELETE) + public void unsetKey( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable String key + ) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + if (!configService.unsetKey(session.getUser(), key)) + { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java new file mode 100644 index 0000000000..bb6050ad4b --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2017-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.config; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import static com.mongodb.client.model.Filters.eq; +import com.mongodb.client.model.IndexOptions; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.UpdateOptions; +import static com.mongodb.client.model.Updates.combine; +import static com.mongodb.client.model.Updates.set; +import static com.mongodb.client.model.Updates.unset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.config.ConfigEntry; +import net.runelite.http.api.config.Configuration; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class ConfigService +{ + private static final Pattern MAYBE_JSON = Pattern.compile("^[\\-0-9{\\[\"]|true|false"); + private static final int MAX_DEPTH = 8; + private static final int MAX_VALUE_LENGTH = 262144; + + private final Gson GSON = RuneLiteAPI.GSON; + private final UpdateOptions upsertUpdateOptions = new UpdateOptions().upsert(true); + + private final MongoCollection mongoCollection; + + @Autowired + public ConfigService( + MongoClient mongoClient, + @Value("${mongo.database}") String databaseName + ) + { + + MongoDatabase database = mongoClient.getDatabase(databaseName); + MongoCollection collection = database.getCollection("config"); + this.mongoCollection = collection; + + // Create unique index on _userId + IndexOptions indexOptions = new IndexOptions().unique(true); + collection.createIndex(Indexes.ascending("_userId"), indexOptions); + } + + private Document getConfig(int userId) + { + return mongoCollection.find(eq("_userId", userId)).first(); + } + + public Configuration get(int userId) + { + Map configMap = getConfig(userId); + + if (configMap == null || configMap.isEmpty()) + { + return new Configuration(Collections.emptyList()); + } + + List config = new ArrayList<>(); + + for (String group : configMap.keySet()) + { + // Reserved keys + if (group.startsWith("_") || group.startsWith("$")) + { + continue; + } + + Map groupMap = (Map) configMap.get(group); + + for (Map.Entry entry : groupMap.entrySet()) + { + String key = entry.getKey(); + Object value = entry.getValue(); + + if (value instanceof Map || value instanceof Collection) + { + value = GSON.toJson(entry.getValue()); + } + else if (value == null) + { + continue; + } + + ConfigEntry configEntry = new ConfigEntry(); + configEntry.setKey(group + "." + key.replace(':', '.')); + configEntry.setValue(value.toString()); + config.add(configEntry); + } + } + + return new Configuration(config); + } + + public List patch(int userID, Configuration config) + { + List failures = new ArrayList<>(); + List sets = new ArrayList<>(config.getConfig().size()); + for (ConfigEntry entry : config.getConfig()) + { + Bson s = setForKV(entry.getKey(), entry.getValue()); + if (s == null) + { + failures.add(entry.getKey()); + } + else + { + sets.add(s); + } + } + + if (sets.size() > 0) + { + mongoCollection.updateOne( + eq("_userId", userID), + combine(sets), + upsertUpdateOptions + ); + } + + return failures; + } + + @Nullable + private Bson setForKV(String key, @Nullable String value) + { + if (key.startsWith("$") || key.startsWith("_")) + { + return null; + } + + String[] split = key.split("\\.", 2); + if (split.length != 2) + { + return null; + } + + String dbKey = split[0] + "." + split[1].replace('.', ':'); + + if (Strings.isNullOrEmpty(value)) + { + return unset(dbKey); + } + + Object jsonValue; + if (!isMaybeJson(value)) + { + if (!validateStr(value)) + { + return null; + } + + jsonValue = value; + } + else + { + if (!validateJson(value)) + { + return null; + } + + jsonValue = parseJsonString(value); + } + return set(dbKey, jsonValue); + } + + public boolean setKey( + int userId, + String key, + @Nullable String value + ) + { + Bson set = setForKV(key, value); + if (set == null) + { + return false; + } + + mongoCollection.updateOne(eq("_userId", userId), + set, + upsertUpdateOptions); + return true; + } + + public boolean unsetKey( + int userId, + String key + ) + { + Bson set = setForKV(key, null); + if (set == null) + { + return false; + } + + mongoCollection.updateOne(eq("_userId", userId), set); + return true; + } + + @VisibleForTesting + static Object parseJsonString(String value) + { + Object jsonValue; + try + { + jsonValue = RuneLiteAPI.GSON.fromJson(value, Object.class); + if (jsonValue == null) + { + return value; + } + else if (jsonValue instanceof Double || jsonValue instanceof Float) + { + Number number = (Number) jsonValue; + if (Math.floor(number.doubleValue()) == number.doubleValue() && !Double.isInfinite(number.doubleValue())) + { + // value is an int or long. 'number' might be truncated so parse it from 'value' + try + { + jsonValue = Integer.parseInt(value); + } + catch (NumberFormatException ex) + { + try + { + jsonValue = Long.parseLong(value); + } + catch (NumberFormatException ex2) + { + + } + } + } + } + } + catch (JsonSyntaxException ex) + { + jsonValue = value; + } + return jsonValue; + } + + @VisibleForTesting + static boolean isMaybeJson(String value) + { + return MAYBE_JSON.matcher(value).find(); + } + + private static boolean validateStr(String value) + { + return value.length() < MAX_VALUE_LENGTH; + } + + @VisibleForTesting + static boolean validateJson(String value) + { + try + { + // I couldn't figure out a better way to do this than a second json parse + JsonElement jsonElement = RuneLiteAPI.GSON.fromJson(value, JsonElement.class); + if (jsonElement == null) + { + return value.length() < MAX_VALUE_LENGTH; + } + return validateObject(jsonElement, 1); + } + catch (JsonSyntaxException ex) + { + // the client submits the string representation of objects which is not always valid json, + // eg. a value with a ':' in it. We just ignore it now. We can't json encode the values client + // side due to them already being strings, which prevents gson from being able to convert them + // to ints/floats/maps etc. + return value.length() < MAX_VALUE_LENGTH; + } + } + + private static boolean validateObject(JsonElement jsonElement, int depth) + { + if (depth >= MAX_DEPTH) + { + return false; + } + + if (jsonElement.isJsonObject()) + { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + for (Map.Entry entry : jsonObject.entrySet()) + { + JsonElement element = entry.getValue(); + + if (!validateObject(element, depth + 1)) + { + return false; + } + } + } + else if (jsonElement.isJsonArray()) + { + JsonArray jsonArray = jsonElement.getAsJsonArray(); + + for (int i = 0; i < jsonArray.size(); ++i) + { + JsonElement element = jsonArray.get(i); + + if (!validateObject(element, depth + 1)) + { + return false; + } + } + } + else if (jsonElement.isJsonPrimitive()) + { + JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive(); + String value = jsonPrimitive.getAsString(); + if (value.length() >= MAX_VALUE_LENGTH) + { + return false; + } + } + + return true; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java b/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java new file mode 100644 index 0000000000..9c799d9873 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed; + +import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.feed.FeedItem; +import net.runelite.http.api.feed.FeedResult; +import net.runelite.http.service.feed.blog.BlogService; +import net.runelite.http.service.feed.osrsnews.OSRSNewsService; +import net.runelite.http.service.feed.twitter.TwitterService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/feed") +@Slf4j +public class FeedController +{ + private final BlogService blogService; + private final TwitterService twitterService; + private final OSRSNewsService osrsNewsService; + + private static class MemoizedFeed + { + final FeedResult feedResult; + final String hash; + + MemoizedFeed(FeedResult feedResult) + { + this.feedResult = feedResult; + + Hasher hasher = Hashing.sha256().newHasher(); + for (FeedItem itemPrice : feedResult.getItems()) + { + hasher.putBytes(itemPrice.getTitle().getBytes(StandardCharsets.UTF_8)).putBytes(itemPrice.getContent().getBytes(StandardCharsets.UTF_8)); + } + HashCode code = hasher.hash(); + hash = code.toString(); + } + } + + private MemoizedFeed memoizedFeed; + + @Autowired + public FeedController(BlogService blogService, TwitterService twitterService, OSRSNewsService osrsNewsService) + { + this.blogService = blogService; + this.twitterService = twitterService; + this.osrsNewsService = osrsNewsService; + } + + @Scheduled(fixedDelay = 10 * 60 * 1000) + public void updateFeed() + { + List items = new ArrayList<>(); + + try + { + items.addAll(blogService.getBlogPosts()); + } + catch (Exception e) + { + log.warn("unable to fetch blogs", e); + } + + try + { + items.addAll(twitterService.getTweets()); + } + catch (Exception e) + { + log.warn("unable to fetch tweets", e); + } + + try + { + items.addAll(osrsNewsService.getNews()); + } + catch (Exception e) + { + log.warn("unable to fetch news", e); + } + + memoizedFeed = new MemoizedFeed(new FeedResult(items)); + } + + @GetMapping + public ResponseEntity getFeed() + { + if (memoizedFeed == null) + { + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .cacheControl(CacheControl.noCache()) + .build(); + } + + return ResponseEntity.ok() + .eTag(memoizedFeed.hash) + .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic()) + .body(memoizedFeed.feedResult); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/feed/blog/BlogService.java b/http-service/src/main/java/net/runelite/http/service/feed/blog/BlogService.java new file mode 100644 index 0000000000..72f7938910 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/feed/blog/BlogService.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed.blog; + +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import net.runelite.http.api.feed.FeedItem; +import net.runelite.http.api.feed.FeedItemType; +import net.runelite.http.service.util.exception.InternalServerErrorException; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +@Service +public class BlogService +{ + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); + + private final OkHttpClient okHttpClient; + private final HttpUrl rssUrl; + + @Autowired + public BlogService( + OkHttpClient okHttpClient, + @Value("${runelite.feed.rssUrl}") String rssUrl + ) + { + this.okHttpClient = okHttpClient; + this.rssUrl = HttpUrl.get(rssUrl); + } + + public List getBlogPosts() throws IOException + { + Request request = new Request.Builder() + .url(rssUrl) + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + throw new IOException("Error getting blog posts: " + response); + } + + try + { + InputStream in = response.body().byteStream(); + Document document = DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(in); + + Element documentElement = document.getDocumentElement(); + NodeList documentItems = documentElement.getElementsByTagName("entry"); + + List items = new ArrayList<>(); + + for (int i = 0; i < Math.min(documentItems.getLength(), 3); i++) + { + Node item = documentItems.item(i); + NodeList children = item.getChildNodes(); + + String title = null; + String summary = null; + String link = null; + long timestamp = -1; + + for (int j = 0; j < children.getLength(); j++) + { + Node childItem = children.item(j); + String nodeName = childItem.getNodeName(); + + switch (nodeName) + { + case "title": + title = childItem.getTextContent(); + break; + case "summary": + summary = childItem.getTextContent().replace("\n", "").trim(); + break; + case "link": + link = childItem.getAttributes().getNamedItem("href").getTextContent(); + break; + case "updated": + timestamp = DATE_FORMAT.parse(childItem.getTextContent()).getTime(); + break; + } + } + + if (title == null || summary == null || link == null || timestamp == -1) + { + throw new InternalServerErrorException("Failed to find title, summary, link and/or timestamp in the blog post feed"); + } + + items.add(new FeedItem(FeedItemType.BLOG_POST, title, summary, link, timestamp)); + } + + return items; + } + catch (ParserConfigurationException | SAXException | ParseException e) + { + throw new InternalServerErrorException("Failed to parse blog posts: " + e.getMessage()); + } + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/feed/osrsnews/OSRSNewsService.java b/http-service/src/main/java/net/runelite/http/service/feed/osrsnews/OSRSNewsService.java new file mode 100644 index 0000000000..e53b2d8044 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/feed/osrsnews/OSRSNewsService.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed.osrsnews; + +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import net.runelite.http.api.feed.FeedItem; +import net.runelite.http.api.feed.FeedItemType; +import net.runelite.http.service.util.exception.InternalServerErrorException; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +@Service +public class OSRSNewsService +{ + private static final SimpleDateFormat PUB_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy '00:00:00 GMT'", Locale.US); + + private final OkHttpClient okHttpClient; + private final HttpUrl rssUrl; + + @Autowired + public OSRSNewsService( + OkHttpClient okHttpClient, + @Value("${runelite.osrsnews.rssUrl}") String rssUrl + ) + { + this.okHttpClient = okHttpClient; + this.rssUrl = HttpUrl.get(rssUrl); + } + + public List getNews() throws IOException + { + Request request = new Request.Builder() + .url(rssUrl) + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + throw new IOException("Error getting OSRS news: " + response); + } + + try + { + InputStream in = response.body().byteStream(); + Document document = DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(in); + + Element documentElement = document.getDocumentElement(); + NodeList documentItems = documentElement.getElementsByTagName("item"); + + List items = new ArrayList<>(); + + for (int i = 0; i < documentItems.getLength(); i++) + { + Node item = documentItems.item(i); + NodeList children = item.getChildNodes(); + + String title = null; + String description = null; + String link = null; + long timestamp = -1; + + for (int j = 0; j < children.getLength(); j++) + { + Node childItem = children.item(j); + String nodeName = childItem.getNodeName(); + + switch (nodeName) + { + case "title": + title = childItem.getTextContent(); + break; + case "description": + description = childItem.getTextContent().replace("\n", "").trim(); + break; + case "link": + link = childItem.getTextContent(); + break; + case "pubDate": + timestamp = PUB_DATE_FORMAT.parse(childItem.getTextContent()).getTime(); + break; + } + } + + if (title == null || description == null || link == null || timestamp == -1) + { + throw new InternalServerErrorException("Failed to find title, description, link and/or timestamp in the OSRS RSS feed"); + } + + items.add(new FeedItem(FeedItemType.OSRS_NEWS, title, description, link, timestamp)); + } + + return items; + } + catch (ParserConfigurationException | SAXException | ParseException e) + { + throw new InternalServerErrorException("Failed to parse OSRS news: " + e.getMessage()); + } + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterOAuth2TokenResponse.java b/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterOAuth2TokenResponse.java new file mode 100644 index 0000000000..24df0b6cf6 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterOAuth2TokenResponse.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed.twitter; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +class TwitterOAuth2TokenResponse +{ + @SerializedName("token_type") + private String tokenType; + + @SerializedName("access_token") + private String token; +} diff --git a/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterService.java b/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterService.java new file mode 100644 index 0000000000..4b7b026646 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterService.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed.twitter; + +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.feed.FeedItem; +import net.runelite.http.api.feed.FeedItemType; +import net.runelite.http.service.util.exception.InternalServerErrorException; +import okhttp3.FormBody; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Service +public class TwitterService +{ + private static final HttpUrl AUTH_URL = HttpUrl.parse("https://api.twitter.com/oauth2/token"); + private static final HttpUrl LIST_STATUSES_URL = HttpUrl.parse("https://api.twitter.com/1.1/lists/statuses.json"); + + private final String credentials; + private final String listId; + private final OkHttpClient okHttpClient; + + private String token; + + @Autowired + public TwitterService( + @Value("${runelite.twitter.consumerkey}") String consumerKey, + @Value("${runelite.twitter.secretkey}") String consumerSecret, + @Value("${runelite.twitter.listid}") String listId, + OkHttpClient okHttpClient + ) + { + this.credentials = consumerKey + ":" + consumerSecret; + this.listId = listId; + this.okHttpClient = okHttpClient; + } + + public List getTweets() throws IOException + { + return getTweets(false); + } + + private List getTweets(boolean hasRetried) throws IOException + { + if (token == null) + { + updateToken(); + } + + HttpUrl url = LIST_STATUSES_URL.newBuilder() + .addQueryParameter("list_id", listId) + .addQueryParameter("count", "15") + .addQueryParameter("include_entities", "false") + .build(); + + Request request = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + token) + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + switch (HttpStatus.valueOf(response.code())) + { + case BAD_REQUEST: + case UNAUTHORIZED: + updateToken(); + if (!hasRetried) + { + return getTweets(true); + } + throw new InternalServerErrorException("Could not auth to Twitter after trying once: " + response); + default: + throw new IOException("Error getting Twitter list: " + response); + } + } + + InputStream in = response.body().byteStream(); + Type listType = new TypeToken>() + { + }.getType(); + List statusesResponse = RuneLiteAPI.GSON + .fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), listType); + + List items = new ArrayList<>(); + + for (TwitterStatusesResponseItem i : statusesResponse) + { + items.add(new FeedItem(FeedItemType.TWEET, + i.getUser().getProfileImageUrl(), + i.getUser().getScreenName(), + i.getText().replace("\n\n", " ").replaceAll("\n", " "), + "https://twitter.com/" + i.getUser().getScreenName() + "/status/" + i.getId(), + getTimestampFromSnowflake(i.getId()))); + } + + return items; + } + } + + private void updateToken() throws IOException + { + String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); + + Request request = new Request.Builder() + .url(AUTH_URL) + .header("Authorization", "Basic " + encodedCredentials) + .post(new FormBody.Builder().add("grant_type", "client_credentials").build()) + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + throw new IOException("Error authing to Twitter: " + response); + } + + InputStream in = response.body().byteStream(); + TwitterOAuth2TokenResponse tokenResponse = RuneLiteAPI.GSON + .fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), TwitterOAuth2TokenResponse.class); + + if (!tokenResponse.getTokenType().equals("bearer")) + { + throw new InternalServerErrorException("Returned token was not a bearer token"); + } + + if (tokenResponse.getToken() == null) + { + throw new InternalServerErrorException("Returned token was null"); + } + + token = tokenResponse.getToken(); + } + } + + /** + * Extracts the UTC timestamp from a Twitter snowflake as per + * https://github.com/client9/snowflake2time/blob/master/python/snowflake.py#L24 + */ + private long getTimestampFromSnowflake(long snowflake) + { + return (snowflake >> 22) + 1288834974657L; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterStatusesResponseItem.java b/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterStatusesResponseItem.java new file mode 100644 index 0000000000..90b37c5021 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterStatusesResponseItem.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed.twitter; + +import lombok.Data; + +@Data +class TwitterStatusesResponseItem +{ + private long id; + private String text; + private TwitterStatusesResponseItemUser user; +} diff --git a/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterStatusesResponseItemUser.java b/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterStatusesResponseItemUser.java new file mode 100644 index 0000000000..94fe9360f9 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/feed/twitter/TwitterStatusesResponseItemUser.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Lotto + * 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.feed.twitter; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +class TwitterStatusesResponseItemUser +{ + @SerializedName("screen_name") + private String screenName; + + @SerializedName("profile_image_url_https") + private String profileImageUrl; +} 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..509f2bdb70 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java @@ -0,0 +1,158 @@ +/* + * 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 com.google.gson.Gson; +import java.io.IOException; +import java.time.Instant; +import java.util.Collection; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.ge.GrandExchangeTrade; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import net.runelite.http.service.util.redis.RedisPool; +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; +import redis.clients.jedis.Jedis; + +@RestController +@RequestMapping("/ge") +public class GrandExchangeController +{ + private static final Gson GSON = RuneLiteAPI.GSON; + + private final GrandExchangeService grandExchangeService; + private final AuthFilter authFilter; + private final RedisPool redisPool; + + @Autowired + public GrandExchangeController(GrandExchangeService grandExchangeService, AuthFilter authFilter, RedisPool redisPool) + { + this.grandExchangeService = grandExchangeService; + this.authFilter = authFilter; + this.redisPool = redisPool; + } + + @PostMapping + public void submit(HttpServletRequest request, HttpServletResponse response, @RequestBody GrandExchangeTrade grandExchangeTrade) throws IOException + { + SessionEntry session = null; + if (request.getHeader(RuneLiteAPI.RUNELITE_AUTH) != null) + { + session = authFilter.handle(request, response); + if (session == null) + { + // error is set here on the response, so we shouldn't continue + return; + } + } + Integer userId = session == null ? null : session.getUser(); + + // We don't keep track of pending trades in the web UI, so only add cancelled or completed trades + if (userId != null && + grandExchangeTrade.getQty() > 0 && + (grandExchangeTrade.isCancel() || grandExchangeTrade.getQty() == grandExchangeTrade.getTotal())) + { + grandExchangeService.add(userId, grandExchangeTrade); + } + + Trade trade = new Trade(); + trade.setBuy(grandExchangeTrade.isBuy()); + trade.setCancel(grandExchangeTrade.isCancel()); + trade.setLogin(grandExchangeTrade.isLogin()); + trade.setItemId(grandExchangeTrade.getItemId()); + trade.setQty(grandExchangeTrade.getQty()); + trade.setDqty(grandExchangeTrade.getDqty()); + trade.setTotal(grandExchangeTrade.getTotal()); + trade.setSpent(grandExchangeTrade.getDspent()); + trade.setOffer(grandExchangeTrade.getOffer()); + trade.setSlot(grandExchangeTrade.getSlot()); + trade.setTime((int) (System.currentTimeMillis() / 1000L)); + trade.setMachineId(request.getHeader(RuneLiteAPI.RUNELITE_MACHINEID)); + trade.setUserId(userId); + trade.setIp(request.getHeader("X-Forwarded-For")); + trade.setUa(request.getHeader("User-Agent")); + trade.setWorldType(grandExchangeTrade.getWorldType()); + trade.setSeq(grandExchangeTrade.getSeq()); + Instant resetTime = grandExchangeTrade.getResetTime(); + trade.setResetTime(resetTime == null ? 0L : resetTime.getEpochSecond()); + + String json = GSON.toJson(trade); + try (Jedis jedis = redisPool.getResource()) + { + jedis.publish("ge", json); + } + } + + @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 GrandExchangeTradeHistory convert(TradeEntry tradeEntry) + { + GrandExchangeTradeHistory grandExchangeTrade = new GrandExchangeTradeHistory(); + 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..227d5e1159 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeService.java @@ -0,0 +1,119 @@ +/* + * 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.beans.factory.annotation.Value; +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; + private final int historyDays; + + @Autowired + public GrandExchangeService( + @Qualifier("Runelite SQL2O") Sql2o sql2o, + @Value("${runelite.ge.history}") int historyDays + ) + { + this.sql2o = sql2o; + this.historyDays = historyDays; + + // 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.getQty()) + .addParameter("price", grandExchangeTrade.getSpent() / grandExchangeTrade.getQty()) + .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 " + historyDays + " day") + .executeUpdate(); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeTradeHistory.java b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeTradeHistory.java new file mode 100644 index 0000000000..c45f5acf4f --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeTradeHistory.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020, 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 GrandExchangeTradeHistory +{ + 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/Trade.java b/http-service/src/main/java/net/runelite/http/service/ge/Trade.java new file mode 100644 index 0000000000..7ad01f322a --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/Trade.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, 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 lombok.Data; +import net.runelite.http.api.worlds.WorldType; + +@Data +class Trade +{ + private boolean buy; + private boolean cancel; + private boolean login; + private int itemId; + private int qty; + private int dqty; + private int total; + private int spent; + private int offer; + private int slot; + private int time; + private String machineId; + private Integer userId; + private String ip; + private String ua; + private WorldType worldType; + private int seq; + private long resetTime; +} 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/http-service/src/main/java/net/runelite/http/service/item/ItemController.java b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java new file mode 100644 index 0000000000..e546344d4c --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2017-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.item; + +import com.google.common.base.Suppliers; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import net.runelite.http.api.item.ItemPrice; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.CacheControl; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/item") +public class ItemController +{ + private static class MemoizedPrices + { + final ItemPrice[] prices; + final String hash; + + MemoizedPrices(ItemPrice[] prices) + { + this.prices = prices; + + Hasher hasher = Hashing.sha256().newHasher(); + for (ItemPrice itemPrice : prices) + { + hasher.putInt(itemPrice.getId()).putInt(itemPrice.getPrice()); + } + HashCode code = hasher.hash(); + hash = code.toString(); + } + } + + private final ItemService itemService; + private final int priceCache; + + private final Supplier memoizedPrices; + + @Autowired + public ItemController( + ItemService itemService, + @Value("${runelite.price.cache}") int priceCache + ) + { + this.itemService = itemService; + this.priceCache = priceCache; + + memoizedPrices = Suppliers.memoizeWithExpiration(() -> new MemoizedPrices(itemService.fetchPrices().stream() + .map(priceEntry -> + { + ItemPrice itemPrice = new ItemPrice(); + itemPrice.setId(priceEntry.getItem()); + itemPrice.setName(priceEntry.getName()); + itemPrice.setPrice(priceEntry.getPrice()); + itemPrice.setWikiPrice(computeWikiPrice(priceEntry)); + return itemPrice; + }) + .toArray(ItemPrice[]::new)), priceCache, TimeUnit.MINUTES); + } + + private static int computeWikiPrice(PriceEntry priceEntry) + { + if (priceEntry.getLow() > 0 && priceEntry.getHigh() > 0) + { + return (priceEntry.getLow() + priceEntry.getHigh()) / 2; + } + else + { + return Math.max(priceEntry.getLow(), priceEntry.getHigh()); + } + } + + @GetMapping("/prices") + public ResponseEntity prices() + { + MemoizedPrices memorizedPrices = this.memoizedPrices.get(); + return ResponseEntity.ok() + .eTag(memorizedPrices.hash) + .cacheControl(CacheControl.maxAge(priceCache, TimeUnit.MINUTES).cachePublic()) + .body(memorizedPrices.prices); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemEntry.java b/http-service/src/main/java/net/runelite/http/service/item/ItemEntry.java new file mode 100644 index 0000000000..41186b1608 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemEntry.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, 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.item; + +import java.time.Instant; +import lombok.Data; +import net.runelite.http.api.item.ItemType; + +@Data +public class ItemEntry +{ + private int id; + private String name; + private String description; + private ItemType type; + private Instant timestamp; +} diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java new file mode 100644 index 0000000000..bc3f03e70c --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2017-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.item; + +import com.google.gson.JsonParseException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Random; +import lombok.extern.slf4j.Slf4j; +import net.runelite.cache.definitions.ItemDefinition; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.item.ItemType; +import net.runelite.http.service.cache.CacheService; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Query; +import org.sql2o.Sql2o; + +@Service +@Slf4j +public class ItemService +{ + private static final String CREATE_ITEMS = "CREATE TABLE IF NOT EXISTS `items` (\n" + + " `id` int(11) NOT NULL,\n" + + " `name` tinytext NOT NULL,\n" + + " `description` tinytext NOT NULL,\n" + + " `type` enum('DEFAULT') NOT NULL,\n" + + " `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" + + " PRIMARY KEY (`id`)\n" + + ") ENGINE=InnoDB"; + + private static final String CREATE_PRICES = "CREATE TABLE IF NOT EXISTS `prices` (\n" + + " `item` int(11) NOT NULL,\n" + + " `price` int(11) NOT NULL,\n" + + " `time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',\n" + + " `fetched_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',\n" + + " UNIQUE KEY `item_time` (`item`,`time`),\n" + + " KEY `item_fetched_time` (`item`,`fetched_time`)\n" + + ") ENGINE=InnoDB"; + + private final Sql2o sql2o; + private final CacheService cacheService; + private final OkHttpClient okHttpClient; + private final HttpUrl itemUrl; + private final HttpUrl priceUrl; + + private int[] tradeableItems; + private final Random random = new Random(); + + @Autowired + public ItemService( + @Qualifier("Runelite SQL2O") Sql2o sql2o, + CacheService cacheService, + OkHttpClient okHttpClient, + @Value("${runelite.item.itemUrl}") String itemUrl, + @Value("${runelite.item.priceUrl}") String priceUrl + ) + { + this.sql2o = sql2o; + this.cacheService = cacheService; + this.okHttpClient = okHttpClient; + this.itemUrl = HttpUrl.get(itemUrl); + this.priceUrl = HttpUrl.get(priceUrl); + + try (Connection con = sql2o.open()) + { + con.createQuery(CREATE_ITEMS) + .executeUpdate(); + + con.createQuery(CREATE_PRICES) + .executeUpdate(); + } + } + + public ItemEntry getItem(int itemId) + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select id, name, description, type from items where id = :id") + .addParameter("id", itemId) + .executeAndFetchFirst(ItemEntry.class); + } + } + + public ItemEntry fetchItem(int itemId) + { + try + { + RSItem rsItem = fetchRSItem(itemId); + + try (Connection con = sql2o.open()) + { + con.createQuery("insert into items (id, name, description, type) values (:id," + + " :name, :description, :type) ON DUPLICATE KEY UPDATE name = :name," + + " description = :description, type = :type") + .addParameter("id", rsItem.getId()) + .addParameter("name", rsItem.getName()) + .addParameter("description", rsItem.getDescription()) + .addParameter("type", rsItem.getType()) + .executeUpdate(); + } + + ItemEntry item = new ItemEntry(); + item.setId(itemId); + item.setName(rsItem.getName()); + item.setDescription(rsItem.getDescription()); + item.setType(ItemType.of(rsItem.getType())); + return item; + } + catch (IOException ex) + { + log.warn("unable to fetch item {}", itemId, ex); + return null; + } + } + + private void fetchPrice(int itemId) + { + RSPrices rsprice; + try + { + rsprice = fetchRSPrices(itemId); + } + catch (IOException ex) + { + log.warn("unable to fetch price for item {}", itemId, ex); + return; + } + + try (Connection con = sql2o.beginTransaction()) + { + Instant now = Instant.now(); + + Query query = con.createQuery("insert into prices (item, price, time, fetched_time) values (:item, :price, :time, :fetched_time) " + + "ON DUPLICATE KEY UPDATE price = VALUES(price), fetched_time = VALUES(fetched_time)"); + + for (Map.Entry entry : rsprice.getDaily().entrySet()) + { + long ts = entry.getKey(); // ms since epoch + int price = entry.getValue(); // gp + + Instant time = Instant.ofEpochMilli(ts); + + query + .addParameter("item", itemId) + .addParameter("price", price) + .addParameter("time", time) + .addParameter("fetched_time", now) + .addToBatch(); + } + + query.executeBatch(); + con.commit(false); + } + } + + public List fetchPrices() + { + try (Connection con = sql2o.beginTransaction()) + { + Query query = con.createQuery("select t2.item, t3.name, t2.time, prices.price, prices.fetched_time, t4.high, t4.low" + + " from (select t1.item as item, max(t1.time) as time from prices t1 group by item) t2" + + " join prices on t2.item=prices.item and t2.time=prices.time" + + " join items t3 on t2.item=t3.id" + + " join wiki_prices t4 on t2.item=t4.item_id"); + return query.executeAndFetch(PriceEntry.class); + } + } + + private RSItem fetchRSItem(int itemId) throws IOException + { + HttpUrl itemUrl = this.itemUrl + .newBuilder() + .addQueryParameter("item", "" + itemId) + .build(); + + Request request = new Request.Builder() + .url(itemUrl) + .build(); + + RSItemResponse itemResponse = fetchJson(request, RSItemResponse.class); + return itemResponse.getItem(); + + } + + private RSPrices fetchRSPrices(int itemId) throws IOException + { + HttpUrl priceUrl = this.priceUrl + .newBuilder() + .addPathSegment(itemId + ".json") + .build(); + + Request request = new Request.Builder() + .url(priceUrl) + .build(); + + return fetchJson(request, RSPrices.class); + } + + private T fetchJson(Request request, Class clazz) throws IOException + { + try (Response response = okHttpClient.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + throw new IOException("Unsuccessful http response: " + response); + } + + InputStream in = response.body().byteStream(); + return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), clazz); + } + catch (JsonParseException ex) + { + throw new IOException(ex); + } + } + + @Scheduled(fixedDelay = 20_000) + public void crawlPrices() + { + if (tradeableItems == null || tradeableItems.length == 0) + { + return; + } + + int idx = random.nextInt(tradeableItems.length); + int id = tradeableItems[idx]; + + log.debug("Fetching price for {}", id); + + // check if the item name or description has changed + fetchItem(id); + fetchPrice(id); + } + + @Scheduled(fixedDelay = 1_800_000) // 30 minutes + public void reloadItems() throws IOException + { + List items = cacheService.getItems(); + if (items.isEmpty()) + { + log.warn("Failed to load any items from cache, item price updating will be disabled"); + } + + tradeableItems = items.stream() + .filter(ItemDefinition::isTradeable) + .mapToInt(ItemDefinition::getId) + .toArray(); + + log.debug("Loaded {} tradeable items", tradeableItems.length); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java b/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java new file mode 100644 index 0000000000..4d29d7e98d --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017, 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.item; + +import java.time.Instant; +import lombok.Data; + +@Data +class PriceEntry +{ + private int item; + private String name; + private int price; + private Instant time; + private Instant fetched_time; + private int high; + private int low; +} diff --git a/http-service/src/main/java/net/runelite/http/service/item/RSItem.java b/http-service/src/main/java/net/runelite/http/service/item/RSItem.java new file mode 100644 index 0000000000..17e3352f6d --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/item/RSItem.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 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.item; + +import lombok.Data; + +@Data +class RSItem +{ + private int id; + private String name; + private String description; + private String type; +} diff --git a/http-service/src/main/java/net/runelite/http/service/item/RSItemResponse.java b/http-service/src/main/java/net/runelite/http/service/item/RSItemResponse.java new file mode 100644 index 0000000000..c0305cd552 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/item/RSItemResponse.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, 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.item; + +import lombok.Data; + +@Data +class RSItemResponse +{ + private RSItem item; +} diff --git a/http-service/src/main/java/net/runelite/http/service/item/RSPrices.java b/http-service/src/main/java/net/runelite/http/service/item/RSPrices.java new file mode 100644 index 0000000000..04331d753e --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/item/RSPrices.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, 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.item; + +import java.util.Map; +import lombok.Data; + +@Data +public class RSPrices +{ + /** + * unix time in ms to price in gp + */ + private Map daily; +} diff --git a/http-service/src/main/java/net/runelite/http/service/loottracker/LootResult.java b/http-service/src/main/java/net/runelite/http/service/loottracker/LootResult.java new file mode 100644 index 0000000000..4cb3375154 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/loottracker/LootResult.java @@ -0,0 +1,42 @@ +/* + * 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.loottracker; + +import java.time.Instant; +import lombok.Data; +import net.runelite.http.api.loottracker.LootRecordType; + +@Data +class LootResult +{ + private int killId; + private Instant first_time; + private Instant last_time; + private LootRecordType type; + private String eventId; + private int amount; + private int itemId; + private int itemQuantity; +} diff --git a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java new file mode 100644 index 0000000000..ef39ac0d50 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018, TheStonedTurtle + * 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.loottracker; + +import com.google.api.client.http.HttpStatusCodes; +import com.google.gson.Gson; +import java.io.IOException; +import java.util.Collection; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.loottracker.LootAggregate; +import net.runelite.http.api.loottracker.LootRecord; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import net.runelite.http.service.util.redis.RedisPool; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import redis.clients.jedis.Jedis; + +@RestController +@RequestMapping("/loottracker") +public class LootTrackerController +{ + private static final Gson GSON = RuneLiteAPI.GSON; + + @Autowired + private LootTrackerService service; + + @Autowired + private RedisPool redisPool; + + @Autowired + private AuthFilter auth; + + @RequestMapping(method = RequestMethod.POST) + public void storeLootRecord(HttpServletRequest request, HttpServletResponse response, @RequestBody Collection records) throws IOException + { + SessionEntry session = null; + if (request.getHeader(RuneLiteAPI.RUNELITE_AUTH) != null) + { + session = auth.handle(request, response); + if (session == null) + { + // error is set here on the response, so we shouldn't continue + return; + } + } + Integer userId = session == null ? null : session.getUser(); + + if (userId != null) + { + service.store(records, userId); + } + response.setStatus(HttpStatusCodes.STATUS_CODE_OK); + + try (Jedis jedis = redisPool.getResource()) + { + jedis.publish("drops", GSON.toJson(records)); + } + } + + @GetMapping + public Collection getLootAggregate(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "count", defaultValue = "1024") int count, @RequestParam(value = "start", defaultValue = "0") int start) throws IOException + { + SessionEntry e = auth.handle(request, response); + if (e == null) + { + response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED); + return null; + } + + return service.get(e.getUser(), count, start); + } + + @DeleteMapping + public void deleteLoot(HttpServletRequest request, HttpServletResponse response, + @RequestParam(required = false) String eventId) throws IOException + { + SessionEntry e = auth.handle(request, response); + if (e == null) + { + response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED); + return; + } + + service.delete(e.getUser(), eventId); + } +} \ No newline at end of file diff --git a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java new file mode 100644 index 0000000000..4836afd97d --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2018, TheStonedTurtle + * 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.loottracker; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.http.api.loottracker.GameItem; +import net.runelite.http.api.loottracker.LootAggregate; +import net.runelite.http.api.loottracker.LootRecord; +import net.runelite.http.api.loottracker.LootRecordType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Query; +import org.sql2o.Sql2o; + +@Service +public class LootTrackerService +{ + private static final String CREATE_KILLS = "CREATE TABLE IF NOT EXISTS `loottracker_kills` (\n" + + " `id` bigint NOT NULL AUTO_INCREMENT,\n" + + " `first_time` timestamp NOT NULL DEFAULT current_timestamp(),\n" + + " `last_time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\n" + + " `accountId` int(11) NOT NULL,\n" + + " `type` enum('NPC','PLAYER','EVENT','PICKPOCKET','UNKNOWN') NOT NULL,\n" + + " `eventId` varchar(255) NOT NULL,\n" + + " `amount` int(11) NOT NULL,\n" + + " PRIMARY KEY (`id`),\n" + + " FOREIGN KEY (accountId) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,\n" + + " INDEX idx_acc_lasttime (`accountId` ,`last_time`),\n" + + " UNIQUE INDEX idx_acc_type_event (`accountId`, `type`, `eventId`),\n" + + " INDEX idx_time (last_time)" + + ") ENGINE=InnoDB;"; + + private static final String CREATE_DROPS = "CREATE TABLE IF NOT EXISTS `loottracker_drops` (\n" + + " `killId` bigint NOT NULL,\n" + + " `itemId` int(11) NOT NULL,\n" + + " `itemQuantity` int(11) NOT NULL,\n" + + " UNIQUE INDEX idx_kill_item (`killId`, `itemId`),\n" + + " FOREIGN KEY (killId) REFERENCES loottracker_kills(id) ON DELETE CASCADE\n" + + ") ENGINE=InnoDB;\n"; + + // Queries for inserting kills + private static final String INSERT_KILL_QUERY = "INSERT INTO loottracker_kills (accountId, type, eventId, amount) VALUES (:accountId, :type, :eventId, :kills) ON DUPLICATE KEY UPDATE amount = amount + :kills"; + private static final String INSERT_DROP_QUERY = "INSERT INTO loottracker_drops (killId, itemId, itemQuantity) VALUES (:killId, :itemId, :itemQuantity) ON DUPLICATE KEY UPDATE itemQuantity = itemQuantity + :itemQuantity"; + + private static final String SELECT_LOOT_QUERY = "SELECT killId,first_time,last_time,type,eventId,amount,itemId,itemQuantity FROM loottracker_kills JOIN loottracker_drops ON loottracker_drops.killId = loottracker_kills.id WHERE accountId = :accountId ORDER BY last_time DESC LIMIT :limit OFFSET :offset"; + + private static final String DELETE_LOOT_ACCOUNT = "DELETE FROM loottracker_kills WHERE accountId = :accountId"; + private static final String DELETE_LOOT_ACCOUNT_EVENTID = "DELETE FROM loottracker_kills WHERE accountId = :accountId AND eventId = :eventId"; + + private final Sql2o sql2o; + private final int historyDays; + + @Autowired + public LootTrackerService( + @Qualifier("Runelite SQL2O") Sql2o sql2o, + @Value("${runelite.loottracker.history}") int historyDays + ) + { + this.sql2o = sql2o; + this.historyDays = historyDays; + + // Ensure necessary tables exist + try (Connection con = sql2o.open()) + { + con.createQuery(CREATE_KILLS).executeUpdate(); + con.createQuery(CREATE_DROPS).executeUpdate(); + } + } + + @RequiredArgsConstructor + @EqualsAndHashCode(exclude = {"kills", "drops"}) + @Getter + private static class AggregateLootRecord + { + final LootRecordType type; + final String eventId; + int kills = 0; + Map drops = new HashMap<>(); + } + + @RequiredArgsConstructor + @EqualsAndHashCode(exclude = "qty") + @Getter + private static class AggregateDrop + { + final int id; + int qty = 0; + } + + private static Collection aggregate(Collection records) + { + Map combinedRecords = new HashMap<>(); + for (LootRecord record : records) + { + AggregateLootRecord r = new AggregateLootRecord(record.getType(), record.getEventId()); + r = combinedRecords.computeIfAbsent(r, (k) -> k); + ++r.kills; + + // Combine drops + for (GameItem gameItem : record.getDrops()) + { + AggregateDrop cd = new AggregateDrop(gameItem.getId()); + cd = r.drops.computeIfAbsent(cd, (k) -> k); + cd.qty += gameItem.getQty(); + } + } + return combinedRecords.values(); + } + + /** + * Store LootRecord + * + * @param records LootRecords to store + * @param accountId runelite account id to tie data too + */ + public void store(Collection records, int accountId) + { + Collection combinedRecords = aggregate(records); + + try (Connection con = sql2o.beginTransaction()) + { + Query killQuery = con.createQuery(INSERT_KILL_QUERY, true); + Query insertDrop = con.createQuery(INSERT_DROP_QUERY); + + for (AggregateLootRecord record : combinedRecords) + { + killQuery + .addParameter("accountId", accountId) + .addParameter("type", record.getType()) + .addParameter("eventId", record.getEventId()) + .addParameter("kills", record.getKills()) + .executeUpdate(); + Object[] keys = con.getKeys(); + + for (AggregateDrop drop : record.getDrops().values()) + { + insertDrop + .addParameter("killId", keys[0]) + .addParameter("itemId", drop.getId()) + .addParameter("itemQuantity", drop.getQty()) + .addToBatch(); + } + + insertDrop.executeBatch(); + } + + con.commit(false); + } + } + + public Collection get(int accountId, int limit, int offset) + { + List lootResults; + + try (Connection con = sql2o.open()) + { + lootResults = con.createQuery(SELECT_LOOT_QUERY) + .addParameter("accountId", accountId) + .addParameter("limit", limit) + .addParameter("offset", offset) + .executeAndFetch(LootResult.class); + } + + LootResult current = null; + List lootRecords = new ArrayList<>(); + List gameItems = new ArrayList<>(); + + for (LootResult lootResult : lootResults) + { + if (current == null || current.getKillId() != lootResult.getKillId()) + { + if (!gameItems.isEmpty()) + { + LootAggregate lootRecord = new LootAggregate(current.getEventId(), current.getType(), gameItems, current.getFirst_time(), current.getLast_time(), current.getAmount()); + lootRecords.add(lootRecord); + + gameItems = new ArrayList<>(); + } + + current = lootResult; + } + + GameItem gameItem = new GameItem(lootResult.getItemId(), lootResult.getItemQuantity()); + gameItems.add(gameItem); + } + + if (!gameItems.isEmpty()) + { + LootAggregate lootRecord = new LootAggregate(current.getEventId(), current.getType(), gameItems, current.getFirst_time(), current.getLast_time(), current.getAmount()); + lootRecords.add(lootRecord); + } + + return lootRecords; + } + + public void delete(int accountId, String eventId) + { + try (Connection con = sql2o.open()) + { + if (eventId == null) + { + con.createQuery(DELETE_LOOT_ACCOUNT) + .addParameter("accountId", accountId) + .executeUpdate(); + } + else + { + con.createQuery(DELETE_LOOT_ACCOUNT_EVENTID) + .addParameter("accountId", accountId) + .addParameter("eventId", eventId) + .executeUpdate(); + } + } + } + + @Scheduled(fixedDelay = 60 * 60 * 1000) + public void expire() + { + try (Connection con = sql2o.open()) + { + con.createQuery("delete from loottracker_kills where last_time < current_timestamp() - interval " + historyDays + " day") + .executeUpdate(); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/pluginhub/PluginHubController.java b/http-service/src/main/java/net/runelite/http/service/pluginhub/PluginHubController.java new file mode 100644 index 0000000000..f9f424dca7 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/pluginhub/PluginHubController.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2020, 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.pluginhub; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; +import javax.servlet.http.HttpServletRequest; +import net.runelite.http.service.util.redis.RedisPool; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; +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.RestController; +import redis.clients.jedis.Jedis; + +@RestController +@RequestMapping("/pluginhub") +public class PluginHubController +{ + @Value("${pluginhub.stats.days:7}") + private int days; + + @Value("${pluginhub.stats.expire:90}") + private int expireDays; + + @Autowired + private RedisPool redisPool; + + private final Cache pluginCache = CacheBuilder.newBuilder() + .maximumSize(512L) + .build(); + + private Map pluginCounts = Collections.emptyMap(); + + @GetMapping + public ResponseEntity> get() + { + if (pluginCounts.isEmpty()) + { + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .cacheControl(CacheControl.noCache()) + .build(); + } + + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic()) + .body(pluginCounts); + } + + @PostMapping + public void submit(HttpServletRequest request, @RequestBody String[] plugins) + { + final String date = Instant.now().atZone(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE); + final String ip = request.getHeader("X-Forwarded-For"); + try (Jedis jedis = redisPool.getResource()) + { + for (String plugin : plugins) + { + if (!plugin.matches("[a-z0-9-]+")) + { + continue; + } + + jedis.pfadd("pluginhub." + plugin + "." + date, ip); + + if (pluginCache.getIfPresent(plugin) == null) + { + jedis.sadd("pluginhub.plugins", plugin); + // additionally set the ttl on the hyperloglog since it might be a new key + jedis.expire("pluginhub." + plugin + "." + date, (int) (Duration.ofDays(expireDays).toMillis() / 1000L)); + + pluginCache.put(plugin, plugin); + } + } + } + } + + @Scheduled(fixedDelay = 1_800_000, initialDelay = 30_000) // 30 minutes with 30 second initial delay + public void rebuildCounts() + { + Map counts = new HashMap<>(); + try (Jedis jedis = redisPool.getResource()) + { + Set plugins = jedis.smembers("pluginhub.plugins"); + ZonedDateTime time = Instant.now().atZone(ZoneOffset.UTC); + + for (String plugin : plugins) + { + // When called with multiple keys, pfcount returns the approximated + // cardinality of the union of the HyperLogLogs. We use this to determine + // the number of users in the last N days. + String[] keys = IntStream.range(0, days - 1) + .mapToObj(time::minusDays) + .map(zdt -> "pluginhub." + plugin + "." + zdt.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .toArray(String[]::new); + long cnt = jedis.pfcount(keys); + if (cnt > 0) + { + counts.put(plugin, cnt); + } + } + } + pluginCounts = counts; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/util/InstantConverter.java b/http-service/src/main/java/net/runelite/http/service/util/InstantConverter.java new file mode 100644 index 0000000000..785ad00266 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/util/InstantConverter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER 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.util; + +import java.sql.Timestamp; +import java.time.Instant; +import org.sql2o.converters.Converter; +import org.sql2o.converters.ConverterException; + +public class InstantConverter implements Converter +{ + @Override + public Instant convert(Object val) throws ConverterException + { + Timestamp ts = (Timestamp) val; + return ts.toInstant(); + } + + @Override + public Object toDatabaseParam(Instant val) + { + return Timestamp.from(val); + } + +} diff --git a/http-service/src/main/java/net/runelite/http/service/util/exception/InternalServerErrorException.java b/http-service/src/main/java/net/runelite/http/service/util/exception/InternalServerErrorException.java new file mode 100644 index 0000000000..62adc5b6fc --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/util/exception/InternalServerErrorException.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER 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.util.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) +public class InternalServerErrorException extends RuntimeException +{ + public InternalServerErrorException(String message) + { + super(message); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/util/exception/NotFoundException.java b/http-service/src/main/java/net/runelite/http/service/util/exception/NotFoundException.java new file mode 100644 index 0000000000..83e04ceca6 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/util/exception/NotFoundException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER 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.util.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not found") +public class NotFoundException extends RuntimeException +{ + +} diff --git a/http-service/src/main/java/net/runelite/http/service/util/redis/RedisPool.java b/http-service/src/main/java/net/runelite/http/service/util/redis/RedisPool.java new file mode 100644 index 0000000000..c0d4a65a29 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/util/redis/RedisPool.java @@ -0,0 +1,102 @@ +/* + * 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.util.redis; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import redis.clients.jedis.Jedis; + +@Component +@Slf4j +public class RedisPool +{ + private final String redisHost; + private final BlockingQueue queue; + + RedisPool(@Value("${redis.pool.size:10}") int queueSize, @Value("${redis.host:localhost}") String redisHost) + { + this.redisHost = redisHost; + + queue = new ArrayBlockingQueue<>(queueSize); + for (int i = 0; i < queueSize; ++i) + { + Jedis jedis = new PooledJedis(redisHost); + queue.offer(jedis); + } + } + + public Jedis getResource() + { + Jedis jedis; + try + { + jedis = queue.poll(1, TimeUnit.SECONDS); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + if (jedis == null) + { + throw new RuntimeException("Unable to acquire connection from pool, timeout"); + } + return jedis; + } + + class PooledJedis extends Jedis + { + PooledJedis(String host) + { + super(host); + } + + @Override + public void close() + { + if (!getClient().isBroken()) + { + queue.offer(this); + return; + } + + log.warn("jedis client is broken, creating new client"); + + try + { + super.close(); + } + catch (Exception e) + { + log.warn("unable to close broken jedis", e); + } + + queue.offer(new PooledJedis(redisHost)); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/wiki/PriceResult.java b/http-service/src/main/java/net/runelite/http/service/wiki/PriceResult.java new file mode 100644 index 0000000000..0723063b02 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/wiki/PriceResult.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER 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.wiki; + +import java.util.Map; +import lombok.Data; + +@Data +class PriceResult +{ + @Data + static class Item + { + private int high; + private int highTime; + private int low; + private int lowTime; + } + + private Map data; +} diff --git a/http-service/src/main/java/net/runelite/http/service/wiki/WikiPriceService.java b/http-service/src/main/java/net/runelite/http/service/wiki/WikiPriceService.java new file mode 100644 index 0000000000..2e32fc1808 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/wiki/WikiPriceService.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER 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.wiki; + +import com.google.gson.JsonSyntaxException; +import java.io.IOException; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Query; +import org.sql2o.Sql2o; + +@Service +@Slf4j +public class WikiPriceService +{ + private static final String CREATE = "CREATE TABLE IF NOT EXISTS `wiki_prices` (\n" + + " `item_id` int(11) NOT NULL,\n" + + " `high` int(11) NOT NULL,\n" + + " `highTime` int(11) NOT NULL,\n" + + " `low` int(11) NOT NULL,\n" + + " `lowTime` int(11) NOT NULL,\n" + + " `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" + + " PRIMARY KEY (`item_id`)\n" + + ") ENGINE=InnoDB;"; + + private final Sql2o sql2o; + private final OkHttpClient okHttpClient; + private final HttpUrl wikiUrl; + + @Autowired + public WikiPriceService( + @Qualifier("Runelite SQL2O") Sql2o sql2o, + OkHttpClient okHttpClient, + @Value("${runelite.wiki.url}") String url + ) + { + this.sql2o = sql2o; + this.okHttpClient = okHttpClient; + this.wikiUrl = HttpUrl.get(url); + + try (Connection con = sql2o.open()) + { + con.createQuery(CREATE).executeUpdate(); + } + } + + @Scheduled(initialDelay = 1000 * 5, fixedDelayString = "${runelite.wiki.poll.ms}") + private void updateDatabase() + { + try + { + PriceResult summary = getPrices(); + + try (Connection con = sql2o.beginTransaction()) + { + Query query = con.createQuery("INSERT INTO wiki_prices (item_id, high, highTime, low, lowTime)" + + " VALUES (:itemId, :high, :highTime, :low, :lowTime)" + + " ON DUPLICATE KEY UPDATE high = VALUES(high), highTime = VALUES(highTime)," + + " low = VALUES(low), lowTime = VALUES(lowTime)"); + + for (Map.Entry entry : summary.getData().entrySet()) + { + Integer itemId = entry.getKey(); + PriceResult.Item item = entry.getValue(); + + query + .addParameter("itemId", itemId) + .addParameter("high", item.getHigh()) + .addParameter("highTime", item.getHighTime()) + .addParameter("low", item.getLow()) + .addParameter("lowTime", item.getLowTime()) + .addToBatch(); + } + + query.executeBatch(); + con.commit(false); + } + } + catch (IOException e) + { + log.warn("Error while updating wiki prices", e); + } + } + + private PriceResult getPrices() throws IOException + { + Request request = new Request.Builder() + .url(wikiUrl) + .header("User-Agent", "RuneLite") + .build(); + + try (Response responseOk = okHttpClient.newCall(request).execute()) + { + if (!responseOk.isSuccessful()) + { + throw new IOException("Error retrieving prices: " + responseOk.message()); + } + + return RuneLiteAPI.GSON.fromJson(responseOk.body().string(), PriceResult.class); + } + catch (JsonSyntaxException ex) + { + throw new IOException(ex); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/worlds/ServiceWorldType.java b/http-service/src/main/java/net/runelite/http/service/worlds/ServiceWorldType.java new file mode 100644 index 0000000000..642cd0440a --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/worlds/ServiceWorldType.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, Lotto + * 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.worlds; + +import net.runelite.http.api.worlds.WorldType; + +enum ServiceWorldType +{ + MEMBERS(WorldType.MEMBERS, 1), + PVP(WorldType.PVP, 1 << 2), + BOUNTY(WorldType.BOUNTY, 1 << 5), + SKILL_TOTAL(WorldType.SKILL_TOTAL, 1 << 7), + HIGH_RISK(WorldType.HIGH_RISK, 1 << 10), + LAST_MAN_STANDING(WorldType.LAST_MAN_STANDING, 1 << 14), + NOSAVE_MODE(WorldType.NOSAVE_MODE, 1 << 25), + TOURNAMENT(WorldType.TOURNAMENT, 1 << 26), + DEADMAN(WorldType.DEADMAN, 1 << 29), + SEASONAL(WorldType.SEASONAL, 1 << 30); + + private final WorldType apiType; + private final int mask; + + ServiceWorldType(WorldType apiType, int mask) + { + this.apiType = apiType; + this.mask = mask; + } + + public WorldType getApiType() + { + return apiType; + } + + public int getMask() + { + return mask; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/worlds/WorldController.java b/http-service/src/main/java/net/runelite/http/service/worlds/WorldController.java new file mode 100644 index 0000000000..a0c71af52c --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/worlds/WorldController.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.worlds; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import net.runelite.http.api.worlds.WorldResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/worlds") +public class WorldController +{ + @Autowired + private WorldsService worldsService; + + private WorldResult worldResult; + + @GetMapping + public ResponseEntity listWorlds() + { + if (worldResult == null) + { + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .cacheControl(CacheControl.noCache()) + .build(); + } + + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic()) + .body(worldResult); + } + + @Scheduled(fixedDelay = 60_000L) + public void refreshWorlds() throws IOException + { + worldResult = worldsService.getWorlds(); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/worlds/WorldsService.java b/http-service/src/main/java/net/runelite/http/service/worlds/WorldsService.java new file mode 100644 index 0000000000..1f8debe7eb --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/worlds/WorldsService.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2017, 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.worlds; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import net.runelite.http.api.worlds.World; +import net.runelite.http.api.worlds.WorldResult; +import net.runelite.http.api.worlds.WorldType; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class WorldsService +{ + private final OkHttpClient okHttpClient; + private final HttpUrl url; + + @Autowired + public WorldsService( + OkHttpClient okHttpClient, + @Value("${runelite.worlds.url}") String url + ) + { + this.okHttpClient = okHttpClient; + this.url = HttpUrl.get(url); + } + + public WorldResult getWorlds() throws IOException + { + Request okrequest = new Request.Builder() + .url(url) + .build(); + + byte[] b; + + try (Response okresponse = okHttpClient.newCall(okrequest).execute()) + { + b = okresponse.body().bytes(); + } + + List worlds = new ArrayList<>(); + ByteBuffer buf = ByteBuffer.wrap(b); + + int length = buf.getInt(); + buf.limit(length + 4); + + int num = buf.getShort() & 0xFFFF; + + for (int i = 0; i < num; ++i) + { + final World.WorldBuilder worldBuilder = World.builder() + .id(buf.getShort() & 0xFFFF) + .types(getTypes(buf.getInt())) + .address(readString(buf)) + .activity(readString(buf)) + .location(buf.get() & 0xFF) + .players(buf.getShort()); + + worlds.add(worldBuilder.build()); + } + + WorldResult result = new WorldResult(); + result.setWorlds(worlds); + return result; + } + + private static EnumSet getTypes(int mask) + { + EnumSet types = EnumSet.noneOf(WorldType.class); + + for (ServiceWorldType type : ServiceWorldType.values()) + { + if ((mask & type.getMask()) != 0) + { + types.add(type.getApiType()); + } + } + + return types; + } + + private static String readString(ByteBuffer buf) + { + byte b; + StringBuilder sb = new StringBuilder(); + + for (;;) + { + b = buf.get(); + + if (b == 0) + { + break; + } + + sb.append((char) b); + } + + return sb.toString(); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/xtea/XteaCache.java b/http-service/src/main/java/net/runelite/http/service/xtea/XteaCache.java new file mode 100644 index 0000000000..7c5f2bb5b0 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xtea/XteaCache.java @@ -0,0 +1,39 @@ +/* + * 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.xtea; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +class XteaCache +{ + private int region; + private int key1; + private int key2; + private int key3; + private int key4; +} diff --git a/http-service/src/main/java/net/runelite/http/service/xtea/XteaController.java b/http-service/src/main/java/net/runelite/http/service/xtea/XteaController.java new file mode 100644 index 0000000000..3868acb8c6 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xtea/XteaController.java @@ -0,0 +1,86 @@ +/* + * 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.xtea; + +import java.util.List; +import java.util.stream.Collectors; +import net.runelite.http.api.xtea.XteaKey; +import net.runelite.http.api.xtea.XteaRequest; +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.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/xtea") +public class XteaController +{ + @Autowired + private XteaService xteaService; + + @RequestMapping(method = POST) + public void submit(@RequestBody XteaRequest xteaRequest) + { + xteaService.submit(xteaRequest); + } + + @GetMapping + public List get() + { + return xteaService.get().stream() + .map(XteaController::entryToKey) + .collect(Collectors.toList()); + } + + @GetMapping("/{region}") + public XteaKey getRegion(@PathVariable int region) + { + XteaEntry xteaRegion = xteaService.getRegion(region); + if (xteaRegion == null) + { + throw new NotFoundException(); + } + + return entryToKey(xteaRegion); + } + + private static XteaKey entryToKey(XteaEntry xe) + { + XteaKey xteaKey = new XteaKey(); + xteaKey.setRegion(xe.getRegion()); + xteaKey.setKeys(new int[] + { + xe.getKey1(), + xe.getKey2(), + xe.getKey3(), + xe.getKey4() + }); + return xteaKey; + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/xtea/XteaEntry.java b/http-service/src/main/java/net/runelite/http/service/xtea/XteaEntry.java new file mode 100644 index 0000000000..c5e60b1119 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xtea/XteaEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017, 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.xtea; + +import java.time.Instant; +import lombok.Data; + +@Data +class XteaEntry +{ + private int region; + private Instant time; + private int rev; + private int key1; + private int key2; + private int key3; + private int key4; +} diff --git a/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java b/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java new file mode 100644 index 0000000000..8a5d651b05 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2017, 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.xtea; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.io.IOException; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import net.runelite.cache.IndexType; +import net.runelite.cache.fs.Container; +import net.runelite.cache.util.Djb2; +import net.runelite.http.api.xtea.XteaKey; +import net.runelite.http.api.xtea.XteaRequest; +import net.runelite.http.service.cache.CacheService; +import net.runelite.http.service.cache.beans.ArchiveEntry; +import net.runelite.http.service.cache.beans.CacheEntry; +import net.runelite.http.service.util.exception.InternalServerErrorException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Query; +import org.sql2o.Sql2o; + +@Service +@Slf4j +public class XteaService +{ + private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `region` int(11) NOT NULL,\n" + + " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" + + " `rev` int(11) NOT NULL,\n" + + " `key1` int(11) NOT NULL,\n" + + " `key2` int(11) NOT NULL,\n" + + " `key3` int(11) NOT NULL,\n" + + " `key4` int(11) NOT NULL,\n" + + " PRIMARY KEY (`id`),\n" + + " KEY `region` (`region`,`time`)\n" + + ") ENGINE=InnoDB"; + + private final Sql2o sql2o; + private final CacheService cacheService; + + private final Cache keyCache = CacheBuilder.newBuilder() + .maximumSize(1024) + .build(); + + @Autowired + public XteaService( + @Qualifier("Runelite SQL2O") Sql2o sql2o, + CacheService cacheService + ) + { + this.sql2o = sql2o; + this.cacheService = cacheService; + + try (Connection con = sql2o.beginTransaction()) + { + con.createQuery(CREATE_SQL) + .executeUpdate(); + } + } + + private XteaEntry findLatestXtea(Connection con, int region) + { + return con.createQuery("select region, time, key1, key2, key3, key4 from xtea " + + "where region = :region " + + "order by time desc " + + "limit 1") + .addParameter("region", region) + .executeAndFetchFirst(XteaEntry.class); + } + + public void submit(XteaRequest xteaRequest) + { + boolean cached = true; + for (XteaKey key : xteaRequest.getKeys()) + { + int region = key.getRegion(); + int[] keys = key.getKeys(); + + if (keys.length != 4) + { + throw new IllegalArgumentException("Key length must be 4"); + } + + XteaCache xteaCache = keyCache.getIfPresent(region); + if (xteaCache == null + || xteaCache.getKey1() != keys[0] + || xteaCache.getKey2() != keys[1] + || xteaCache.getKey3() != keys[2] + || xteaCache.getKey4() != keys[3]) + { + cached = false; + keyCache.put(region, new XteaCache(region, keys[0], keys[1], keys[2], keys[3])); + } + } + + if (cached) + { + return; + } + + try (Connection con = sql2o.beginTransaction()) + { + CacheEntry cache = cacheService.findMostRecent(); + + if (cache == null) + { + throw new InternalServerErrorException("No most recent cache"); + } + + Query query = null; + + for (XteaKey key : xteaRequest.getKeys()) + { + int region = key.getRegion(); + int[] keys = key.getKeys(); + + XteaEntry xteaEntry = findLatestXtea(con, region); + + // already have these? + if (xteaEntry != null + && xteaEntry.getKey1() == keys[0] + && xteaEntry.getKey2() == keys[1] + && xteaEntry.getKey3() == keys[2] + && xteaEntry.getKey4() == keys[3]) + { + continue; + } + + ArchiveEntry archiveEntry = archiveForRegion(cache, region); + if (archiveEntry == null) + { + // the client sends 0,0,0,0 for non-existent regions, just ignore them + continue; + } + + if (!checkKeys(archiveEntry, keys)) + { + continue; + } + + if (query == null) + { + query = con.createQuery("insert into xtea (region, rev, key1, key2, key3, key4) " + + "values (:region, :rev, :key1, :key2, :key3, :key4)"); + } + + query.addParameter("region", region) + .addParameter("rev", xteaRequest.getRevision()) + .addParameter("key1", keys[0]) + .addParameter("key2", keys[1]) + .addParameter("key3", keys[2]) + .addParameter("key4", keys[3]) + .addToBatch(); + + log.debug("Inserted keys for {}: {}, {}, {}, {}", region, keys[0], keys[1], keys[2], keys[3]); + } + + if (query != null) + { + query.executeBatch(); + con.commit(false); + } + } + } + + public List get() + { + try (Connection con = sql2o.open()) + { + return con.createQuery( + "select t1.region, t2.time, t2.rev, t2.key1, t2.key2, t2.key3, t2.key4 from " + + "(select region,max(id) as id from xtea group by region) t1 " + + "join xtea t2 on t1.id = t2.id") + .executeAndFetch(XteaEntry.class); + } + } + + public XteaEntry getRegion(int region) + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select region, time, rev, key1, key2, key3, key4 from xtea " + + "where region = :region order by time desc limit 1") + .addParameter("region", region) + .executeAndFetchFirst(XteaEntry.class); + } + } + + private ArchiveEntry archiveForRegion(CacheEntry cache, int regionId) + { + int x = regionId >>> 8; + int y = regionId & 0xFF; + + String archiveName = new StringBuilder() + .append('l') + .append(x) + .append('_') + .append(y) + .toString(); + int archiveNameHash = Djb2.hash(archiveName); + + return cacheService.findArchiveForTypeAndName(cache, IndexType.MAPS, archiveNameHash); + } + + private boolean checkKeys(ArchiveEntry archiveEntry, int[] keys) + { + byte[] data = cacheService.getArchive(archiveEntry); + if (data == null) + { + throw new InternalServerErrorException("Unable to get archive data for archive " + archiveEntry.getArchiveId()); + } + + try + { + Container.decompress(data, keys); + return true; + } + catch (IOException ex) + { + return false; + } + } +} diff --git a/http-service/src/main/resources/application-dev.yaml b/http-service/src/main/resources/application-dev.yaml new file mode 100644 index 0000000000..2b24790c51 --- /dev/null +++ b/http-service/src/main/resources/application-dev.yaml @@ -0,0 +1,29 @@ +# Enable debug logging +debug: true +logging.level.net.runelite: DEBUG + +# Development data sources +datasource: + runelite: + jndiName: + driverClassName: org.mariadb.jdbc.Driver + type: org.mariadb.jdbc.MariaDbDataSource + url: jdbc:mariadb://localhost:3306/runelite + username: runelite + password: runelite + runelite-cache: + jndiName: + driverClassName: org.mariadb.jdbc.Driver + type: org.mariadb.jdbc.MariaDbDataSource + url: jdbc:mariadb://localhost:3306/cache + username: runelite + password: runelite + +# Development mongo +mongo: + jndiName: + host: mongodb://localhost:27017 + +# Development oauth callback (without proxy) +oauth: + callback: http://localhost:8080/account/callback diff --git a/http-service/src/main/resources/application.yaml b/http-service/src/main/resources/application.yaml new file mode 100644 index 0000000000..7431545cbb --- /dev/null +++ b/http-service/src/main/resources/application.yaml @@ -0,0 +1,61 @@ +datasource: + runelite: + jndiName: java:comp/env/jdbc/runelite + runelite-cache: + jndiName: java:comp/env/jdbc/runelite-cache2 + +# By default Spring tries to register the datasource as an MXBean, +# so if multiple apis are deployed on one web container with +# shared datasource it tries to register it multiples times and +# fails when starting the 2nd api +spring.jmx.enabled: false + +# Google OAuth client +oauth: + client-id: + client-secret: + callback: https://api.runelite.net/oauth/ + +# Minio client storage for cache +minio: + endpoint: http://localhost:9000 + accesskey: AM54M27O4WZK65N6F8IP + secretkey: /PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP + bucket: runelite + +# Redis client +redis: + pool.size: 10 + host: tcp://localhost:6379 + +mongo: + jndiName: java:comp/env/mongodb/runelite + database: runelite + +runelite: + version: @project.version@ + commit: @git.commit.id.abbrev@ + dirty: @git.dirty@ + # Twitter client for feed + twitter: + consumerkey: + secretkey: + listid: 1185897074786742273 + ge: + history: 90 # days + loottracker: + history: 90 # days + wiki: + poll.ms: 300000 # 5 minutes + url: https://prices.runescape.wiki/api/v1/osrs/latest + price: + cache: 30 # minutes + feed: + rssUrl: https://runelite.net/atom.xml + worlds: + url: http://www.runescape.com/g=oldscape/slr.ws?order=LPWM + osrsnews: + rssUrl: https://services.runescape.com/m=news/latest_news.rss?oldschool=true + item: + itemUrl: https://services.runescape.com/m=itemdb_oldschool/api/catalogue/detail.json + priceUrl: https://services.runescape.com/m=itemdb_oldschool/api/graph \ No newline at end of file diff --git a/http-service/src/main/templates/markdown.hbs b/http-service/src/main/templates/markdown.hbs new file mode 100644 index 0000000000..1beacd052a --- /dev/null +++ b/http-service/src/main/templates/markdown.hbs @@ -0,0 +1,110 @@ +{{#info}} +# {{title}} +{{join schemes " | "}}://{{host}}{{basePath}} + +{{description}} + +{{#contact}} +[**Contact the developer**](mailto:{{email}}) +{{/contact}} + +**Version** {{version}} + +{{#if termsOfService}} +[**Terms of Service**]({{termsOfService}}) +{{/if}} + +{{/info}} + +{{#if consumes}}__Consumes:__ {{join consumes ", "}}{{/if}} + +{{#if produces}}__Produces:__ {{join produces ", "}}{{/if}} + +{{#if securityDefinitions}} +# Security Definitions +{{> security}} +{{/if}} + +
+Table Of Contents +[toc] +
+ +# APIs + +{{#each paths}} +## {{@key}} +{{#this}} +{{#get}} +### GET +{{> operation}} +{{/get}} + +{{#put}} +### PUT +{{> operation}} +{{/put}} + +{{#post}} +### POST + +{{> operation}} + +{{/post}} + +{{#delete}} +### DELETE +{{> operation}} +{{/delete}} + +{{#option}} +### OPTION +{{> operation}} +{{/option}} + +{{#patch}} +### PATCH +{{> operation}} +{{/patch}} + +{{#head}} +### HEAD +{{> operation}} +{{/head}} + +{{/this}} +{{/each}} + +# Definitions +{{#each definitions}} +## {{@key}} + + + + + + + + + + {{#each this.properties}} + + + + + + + + {{/each}} +
nametyperequireddescriptionexample
{{@key}} + {{#ifeq type "array"}} + {{#items.$ref}} + {{type}}[{{basename items.$ref}}] + {{/items.$ref}} + {{^items.$ref}}{{type}}[{{items.type}}]{{/items.$ref}} + {{else}} + {{#$ref}}{{basename $ref}}{{/$ref}} + {{^$ref}}{{type}}{{#format}} ({{format}}){{/format}}{{/$ref}} + {{/ifeq}} + {{#required}}required{{/required}}{{^required}}optional{{/required}}{{#description}}{{{description}}}{{/description}}{{^description}}-{{/description}}{{example}}
+{{/each}} diff --git a/http-service/src/main/templates/operation.hbs b/http-service/src/main/templates/operation.hbs new file mode 100644 index 0000000000..f7015850b8 --- /dev/null +++ b/http-service/src/main/templates/operation.hbs @@ -0,0 +1,71 @@ +{{#deprecated}}-deprecated-{{/deprecated}} +{{summary}} + +{{description}} + +{{#if externalDocs.url}}{{externalDocs.description}}. [See external documents for more details]({{externalDocs.url}}) +{{/if}} + +{{#if security}} +#### Security +{{/if}} + +{{#security}} +{{#each this}} +* {{@key}} +{{#this}} * {{this}} +{{/this}} +{{/each}} +{{/security}} + +#### Request + +{{#if consumes}}__Content-Type:__ {{join consumes ", "}}{{/if}} + +##### Parameters +{{#if parameters}} + + + + + + + + + +{{/if}} + +{{#parameters}} + + + + + + +{{#ifeq in "body"}} + +{{else}} + {{#ifeq type "array"}} + + {{else}} + + {{/ifeq}} +{{/ifeq}} + +{{/parameters}} +{{#if parameters}} +
NameLocated inRequiredDescriptionDefaultSchema
{{name}}{{in}}{{#if required}}yes{{else}}no{{/if}}{{description}}{{#if pattern}} (**Pattern**: `{{pattern}}`){{/if}} - + {{#ifeq schema.type "array"}}Array[{{basename schema.items.$ref}}]{{/ifeq}} + {{#schema.$ref}}{{basename schema.$ref}} {{/schema.$ref}} + Array[{{items.type}}] ({{collectionFormat}}){{type}} {{#format}}({{format}}){{/format}}
+{{/if}} + + +#### Response + +{{#if produces}}__Content-Type:__ {{join produces ", "}}{{/if}} + +| Status Code | Reason | Response Model | +|-------------|-------------|----------------| +{{#each responses}}| {{@key}} | {{description}} | {{#schema.$ref}}{{basename schema.$ref}}{{/schema.$ref}}{{#ifeq schema.type "array"}}Array[{{basename schema.items.$ref}}]{{/ifeq}}{{^schema}} - {{/schema}}| +{{/each}} diff --git a/http-service/src/main/templates/security.hbs b/http-service/src/main/templates/security.hbs new file mode 100644 index 0000000000..04f86e8380 --- /dev/null +++ b/http-service/src/main/templates/security.hbs @@ -0,0 +1,88 @@ +{{#each securityDefinitions}} +### {{@key}} +{{#this}} +{{#ifeq type "oauth2"}} + + + + + +{{#if description}} + + + + +{{/if}} +{{#if authorizationUrl}} + + + + +{{/if}} +{{#if flow}} + + + + +{{/if}} +{{#if tokenUrl}} + + + + +{{/if}} +{{#if scopes}} + + +{{#each scopes}} + + + + +{{/each}} + +{{/if}} +
type{{type}}
description{{description}}
authorizationUrl{{authorizationUrl}}
flow{{flow}}
tokenUrl{{tokenUrl}}
scopes{{@key}}{{this}}
+{{/ifeq}} +{{#ifeq type "apiKey"}} + + + + + +{{#if description}} + + + + +{{/if}} +{{#if name}} + + + + +{{/if}} +{{#if in}} + + + + +{{/if}} +
type{{type}}
description{{description}}
name{{name}}
in{{in}}
+{{/ifeq}} +{{#ifeq type "basic"}} + + + + + +{{#if description}} + + + + +{{/if}} +
type{{type}}
description{{description}}
+{{/ifeq}} +{{/this}} +{{/each}} \ No newline at end of file diff --git a/http-service/src/main/templates/template.html.hbs b/http-service/src/main/templates/template.html.hbs new file mode 100644 index 0000000000..da587c2cc4 --- /dev/null +++ b/http-service/src/main/templates/template.html.hbs @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + {{info.title}} {{info.version}} + + + + + \ No newline at end of file diff --git a/http-service/src/main/webapp/WEB-INF/web.xml b/http-service/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..0b07dbb973 --- /dev/null +++ b/http-service/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,32 @@ + + + + RuneLite API + \ No newline at end of file diff --git a/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java b/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java new file mode 100644 index 0000000000..27320f3f72 --- /dev/null +++ b/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java @@ -0,0 +1,85 @@ +/* + * 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.config; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebMvcTest(ConfigController.class) +@ActiveProfiles("test") +public class ConfigControllerTest +{ + @Autowired + private MockMvc mockMvc; + + @MockBean + private ConfigService configService; + + @MockBean + private AuthFilter authFilter; + + @Before + public void before() throws IOException + { + when(authFilter.handle(any(HttpServletRequest.class), any(HttpServletResponse.class))) + .thenReturn(mock(SessionEntry.class)); + + when(configService.setKey(anyInt(), anyString(), anyString())).thenReturn(true); + } + + @Test + public void testSetKey() throws Exception + { + mockMvc.perform(put("/config/key") + .content("value") + .contentType(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()); + + verify(configService).setKey(anyInt(), eq("key"), eq("value")); + } +} \ No newline at end of file diff --git a/http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java b/http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java new file mode 100644 index 0000000000..a08a9a7160 --- /dev/null +++ b/http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java @@ -0,0 +1,72 @@ +/* + * 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.config; + +import com.google.common.collect.ImmutableMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +public class ConfigServiceTest +{ + @Test + public void testParseJsonString() + { + assertEquals(1, ConfigService.parseJsonString("1")); + assertEquals(3.14, ConfigService.parseJsonString("3.14")); + assertEquals(1L << 32, ConfigService.parseJsonString("4294967296")); + assertEquals("test", ConfigService.parseJsonString("test")); + assertEquals("test", ConfigService.parseJsonString("\"test\"")); + assertEquals(ImmutableMap.of("key", "value"), ConfigService.parseJsonString("{\"key\": \"value\"}")); + } + + @Test + public void testValidateJson() + { + assertTrue(ConfigService.validateJson("1")); + assertTrue(ConfigService.validateJson("3.14")); + assertTrue(ConfigService.validateJson("test")); + assertTrue(ConfigService.validateJson("\"test\"")); + assertTrue(ConfigService.validateJson("key:value")); + assertTrue(ConfigService.validateJson("{\"key\": \"value\"}")); + assertTrue(ConfigService.validateJson("\n")); + } + + @Test + public void testMaybeJson() + { + assertFalse(ConfigService.isMaybeJson("string")); + assertFalse(ConfigService.isMaybeJson("string with spaces")); + + assertTrue(ConfigService.isMaybeJson("true")); + assertTrue(ConfigService.isMaybeJson("false")); + assertTrue(ConfigService.isMaybeJson("1")); + assertTrue(ConfigService.isMaybeJson("1.2")); + assertTrue(ConfigService.isMaybeJson("\"quote\"")); + assertTrue(ConfigService.isMaybeJson("{\"key\": \"value\"}")); + assertTrue(ConfigService.isMaybeJson("[42]")); + } +} \ No newline at end of file diff --git a/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java b/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java new file mode 100644 index 0000000000..68e64270d6 --- /dev/null +++ b/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java @@ -0,0 +1,104 @@ +/* + * 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.loottracker; + +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoField; +import java.util.Collections; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.loottracker.GameItem; +import net.runelite.http.api.loottracker.LootRecord; +import net.runelite.http.api.loottracker.LootRecordType; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import net.runelite.http.service.util.redis.RedisPool; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import redis.clients.jedis.Jedis; + +@RunWith(SpringRunner.class) +@WebMvcTest(LootTrackerController.class) +@ActiveProfiles("test") +public class LootTrackerControllerTest +{ + @Autowired + private MockMvc mockMvc; + + @MockBean + private LootTrackerService lootTrackerService; + + @MockBean + private AuthFilter authFilter; + + @MockBean + private RedisPool redisPool; + + @Before + public void before() throws IOException + { + when(authFilter.handle(any(HttpServletRequest.class), any(HttpServletResponse.class))) + .thenReturn(mock(SessionEntry.class)); + + when(redisPool.getResource()).thenReturn(mock(Jedis.class)); + } + + @Test + public void storeLootRecord() throws Exception + { + LootRecord lootRecord = new LootRecord(); + lootRecord.setType(LootRecordType.NPC); + lootRecord.setTime(Instant.now().with(ChronoField.NANO_OF_SECOND, 0)); + lootRecord.setDrops(Collections.singletonList(new GameItem(4151, 1))); + + String data = RuneLiteAPI.GSON.toJson(Collections.singletonList(lootRecord)); + mockMvc.perform(post("/loottracker") + .header(RuneLiteAPI.RUNELITE_AUTH, UUID.nameUUIDFromBytes("test".getBytes())) + .content(data) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + verify(lootTrackerService).store(eq(Collections.singletonList(lootRecord)), anyInt()); + } +} \ No newline at end of file diff --git a/http-service/src/test/java/net/runelite/http/service/worlds/WorldsServiceTest.java b/http-service/src/test/java/net/runelite/http/service/worlds/WorldsServiceTest.java new file mode 100644 index 0000000000..b6275d7c48 --- /dev/null +++ b/http-service/src/test/java/net/runelite/http/service/worlds/WorldsServiceTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, 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.worlds; + +import java.io.IOException; +import java.io.InputStream; +import net.runelite.http.api.worlds.World; +import net.runelite.http.api.worlds.WorldResult; +import net.runelite.http.api.worlds.WorldType; +import okhttp3.OkHttpClient; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okio.Buffer; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sql2o.tools.IOUtils; + +public class WorldsServiceTest +{ + @Rule + public final MockWebServer server = new MockWebServer(); + + @Before + public void before() throws IOException + { + InputStream in = WorldsServiceTest.class.getResourceAsStream("worldlist"); + byte[] worldData = IOUtils.toByteArray(in); + + Buffer buffer = new Buffer(); + buffer.write(worldData); + + server.enqueue(new MockResponse().setBody(buffer)); + } + + @Test + public void testListWorlds() throws Exception + { + WorldsService worlds = new WorldsService(new OkHttpClient(), server.url("/").toString()); + + WorldResult worldResult = worlds.getWorlds(); + assertEquals(82, worldResult.getWorlds().size()); + + World world = worldResult.findWorld(385); + assertNotNull(world); + assertTrue(world.getTypes().contains(WorldType.SKILL_TOTAL)); + + for (World testWorld : worldResult.getWorlds()) + { + assertNotNull("Missing a region in WorldRegion enum", testWorld.getRegion()); + } + } + +} diff --git a/http-service/src/test/resources/application-test.yaml b/http-service/src/test/resources/application-test.yaml new file mode 100644 index 0000000000..3a8e416a54 --- /dev/null +++ b/http-service/src/test/resources/application-test.yaml @@ -0,0 +1,21 @@ +# Use in-memory database for tests +datasource: + runelite: + jndiName: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:runelite + runelite-cache: + jndiName: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:cache + runelite-tracker: + jndiName: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:xptracker + +mongo: + jndiName: + host: mongodb://localhost:27017 \ No newline at end of file diff --git a/http-service/src/test/resources/net/runelite/http/service/worlds/worldlist b/http-service/src/test/resources/net/runelite/http/service/worlds/worldlist new file mode 100644 index 0000000000000000000000000000000000000000..1d1360e5795d066b068e095cf2a033a504405ad6 GIT binary patch literal 5000 zcmeHKOK%%h6uu{Nt&HNnr;&XfUL@?A&0Xv2`>KbrNjykW>2xs2%({goUE2ojRl+UsP2YF3kRF>f)3pl!mq&wAvxSj*vi<` zL+jfJeGeWY1bdoC8M!GddJqr^AHcJOR^c?>rfy383=~o`EgbG-%g){BFW?y(jytE( z4^`J~%Z8WuXg~ztgnJ0Z;z}$8;g*g%lgkR9g4r$CbK^cTzJkXHugooe2$gZDT;KJ# z5d9785>A!3$~+oBz{>`Iajg@H&p1F21Jocycp5{fJHd`3fGmV+CeBADvbNG8Q=gAg+81nq!QO^DM%tc zH84YRq~rOT1m2nR1XRk~LWtMl z2?8qTYY^fMxR-z|?j4iUn{bAJO8M>y@ir_|5ZBq9v=3&$j#M`;+($4^Kz81CLVOIX z1XRx3&dih&0dYfNN^lGwCz$dW(8M2bzu9)u-NWLb!G;%fdpdSwciSv#@4%DfT!$yP zy1S%QDC@Z54Bq;|-nLCR)x1LhZ{@=TRN|n~^~Azc4`7oJ9Bu?UG(mg|W^`uNI=ou5 zV}1u~1XSjxXa=61?0Kqh-a8QA!y^>W{gDj_Pwfn+1K_|vg{*8_w7Fr@Z4pEK3@ao= zi5q7TSJ7xN?r_2eTlg3smkZiQ>aTDSse5UI%lt;=mP-)7LxqI1c!!s9`_+D14&VOS z7COW1M get() throws IOException { HttpUrl url = apiBase.newBuilder() - .addPathSegment("xtea") - .build(); + .addPathSegment("xtea") + .build(); Request request = new Request.Builder() - .url(url) - .build(); + .url(url) + .build(); try (Response response = client.newCall(request).execute()) { @@ -125,13 +124,13 @@ public class XteaClient public XteaKey get(int region) throws IOException { HttpUrl url = apiBase.newBuilder() - .addPathSegment("xtea") - .addPathSegment(Integer.toString(region)) - .build(); + .addPathSegment("xtea") + .addPathSegment(Integer.toString(region)) + .build(); Request request = new Request.Builder() - .url(url) - .build(); + .url(url) + .build(); try (Response response = client.newCall(request).execute()) {