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?
+ * This class addresses this problem by adding type information to the
+ * serialized JSON and honoring that type information when the JSON is
+ * deserialized:
+ * 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.
+ * 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.
+ *
+ */
+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 extends T> 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 extends T> 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 extends WebsocketMessage> message : MESSAGES)
+ {
+ factory.registerSubtype(message);
+ }
+
+ for (Class extends WebsocketMessage> 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