Merge branch 'master' into loot-tracker-reset

This commit is contained in:
PKLite
2019-06-28 02:35:03 -04:00
432 changed files with 10378 additions and 14155 deletions

View File

@@ -42,6 +42,11 @@ public class AALoad extends Instruction implements ArrayLoad
super(instructions, type);
}
public AALoad(Instructions instructions)
{
super(instructions, InstructionType.AALOAD);
}
@Override
public InstructionContext execute(Frame frame)
{

View File

@@ -154,7 +154,7 @@ public class Deob
public static boolean isObfuscated(String name)
{
return name.length() <= OBFUSCATED_NAME_MAX_LEN || name.startsWith("method") || name.startsWith("vmethod") || name.startsWith("field") || name.startsWith("class");
return name.length() <= OBFUSCATED_NAME_MAX_LEN || name.startsWith("method") || name.startsWith("vmethod") || name.startsWith("field") || name.startsWith("class") || name.startsWith("__");
}
private static void runMath(ClassGroup group)

View File

@@ -0,0 +1,75 @@
package net.runelite.deob.updater;
import java.io.File;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Annotations;
import net.runelite.deob.Deob;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.DeobTestProperties;
import net.runelite.deob.util.JarUtil;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AnnotationCleaner
{
private final Logger log = LoggerFactory.getLogger(AnnotationCleaner.class);
@Rule
public DeobTestProperties properties = new DeobTestProperties();
@Test
public void checkMappings() throws Exception
{
File client = new File(properties.getRsClient());
ClassGroup group = JarUtil.loadJar(client);
for (ClassFile c : group.getClasses())
{
if (c.getName().contains("runelite"))
{
continue;
}
log.debug("Checking {}", c.toString());
for (Field f : c.getFields())
{
Annotations an = f.getAnnotations();
String fieldName = f.getName();
String exportedName = DeobAnnotations.getExportedName(an);
if (exportedName == null)
{
assertTrue("Field " + fieldName + " isn't obfuscated but doesn't have @Export.", Deob.isObfuscated(fieldName) || fieldName.contains("$"));
continue;
}
assertEquals("Field " + fieldName + " has " + exportedName + " in @Export", fieldName, exportedName);
}
for (Method m : c.getMethods())
{
Annotations an = m.getAnnotations();
String fieldName = m.getName();
String exportedName = DeobAnnotations.getExportedName(an);
if (exportedName == null)
{
assertTrue("Method " + fieldName + " isn't obfuscated but doesn't have @Export.", Deob.isObfuscated(fieldName) || fieldName.endsWith("init>") || fieldName.equals("values") || fieldName.equals("valueOf") || fieldName.startsWith("compareTo") || fieldName.equals("equals") || fieldName.equals("hashCode") || fieldName.equals("compare"));
continue;
}
assertEquals("Method " + fieldName + " has " + exportedName + " in @Export", fieldName, exportedName);
}
}
}
}

View File

@@ -1,3 +1,3 @@
rs.client=${net.runelite.rs:rs-client:jar}
rs.version=180
rs.version=${rs.version}
vanilla.client=${net.runelite.rs:vanilla:jar}

View File

@@ -268,7 +268,15 @@ public class RuneLiteAPI
{
try
{
String jsonData = new String(downloadUrl(new URL(GITHUB_API)));
byte[] commits = downloadUrl(new URL(GITHUB_API));
if (commits == null)
{
return null;
}
String jsonData = new String(commits);
for (String s : jsonData.split("\n"))
{
if (s.startsWith("\"sha\":"))

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.animation;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AnimationClient
{
private static final MediaType JSON = MediaType.parse("application/json");
private static final Logger logger = LoggerFactory.getLogger(AnimationClient.class);
public void submit(AnimationRequest animationRequest)
{
String json = RuneLiteAPI.GSON.toJson(animationRequest);
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("animation")
.build();
logger.debug("Built URI: {}", url);
Request request = new Request.Builder()
.post(RequestBody.create(JSON, json))
.url(url)
.build();
try
{
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
logger.debug("animation response " + response.code());
}
}
catch (IOException e)
{
e.printStackTrace();
}
RuneLiteAPI.RLP_CLIENT.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(Call call, IOException e)
{
logger.warn("unable to submit animations", e);
}
@Override
public void onResponse(Call call, Response response)
{
try
{
if (!response.isSuccessful())
{
logger.debug("unsuccessful animation response");
}
}
finally
{
response.close();
}
}
});
}
public List<AnimationKey> get() throws IOException
{
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("animation")
.build();
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
InputStream in = response.body().byteStream();
// CHECKSTYLE:OFF
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), new TypeToken<List<AnimationKey>>()
{
}.getType());
// CHECKSTYLE:ON
}
catch (JsonParseException ex)
{
throw new IOException(ex);
}
}
public AnimationKey get(int npcid) throws IOException
{
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("animation")
.addPathSegment(Integer.toString(npcid))
.build();
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
InputStream in = response.body().byteStream();
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), AnimationKey.class);
}
catch (JsonParseException ex)
{
throw new IOException(ex);
}
}
}

View File

@@ -22,25 +22,30 @@
* (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;
package net.runelite.http.api.animation;
public class UserInfo
public class AnimationKey
{
private String email;
private int npcid;
private int[] animations;
@Override
public String toString()
public int getNPCId()
{
return "UserInfo{" + "email=" + email + '}';
return npcid;
}
public String getEmail()
public void setNPCId(int npcid)
{
return email;
this.npcid = npcid;
}
public void setEmail(String email)
public int[] getAnimations()
{
this.email = email;
return animations;
}
public void setAnimations(int[] keys)
{
this.animations = keys;
}
}

View File

@@ -22,32 +22,33 @@
* (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;
package net.runelite.http.api.animation;
import java.util.UUID;
import java.util.ArrayList;
import java.util.List;
public class State
public class AnimationRequest
{
private UUID uuid;
private String apiVersion;
private int revision;
private List<AnimationKey> keys = new ArrayList<>();
public UUID getUuid()
public int getRevision()
{
return uuid;
return revision;
}
public void setUuid(UUID uuid)
public void setRevision(int revision)
{
this.uuid = uuid;
this.revision = revision;
}
public String getApiVersion()
public List<AnimationKey> getKeys()
{
return apiVersion;
return keys;
}
public void setApiVersion(String apiVersion)
public void addKey(AnimationKey key)
{
this.apiVersion = apiVersion;
keys.add(key);
}
}

View File

@@ -27,6 +27,8 @@ package net.runelite.http.api.chat;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.io.InputStreamReader;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.HttpUrl;
@@ -36,6 +38,10 @@ import okhttp3.Response;
public class ChatClient
{
private static final Predicate<String> LAYOUT_VALIDATOR = Pattern
.compile("\\[[A-Z]+]:(\\s*\\w+\\s*(\\([A-Za-z]+\\))?,?)+")
.asPredicate();
public boolean submitKc(String username, String boss, int kc) throws IOException
{
HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
@@ -219,7 +225,7 @@ public class ChatClient
public boolean submitGc(String username, int gc) throws IOException
{
HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("chat")
.addPathSegment("gc")
.addQueryParameter("name", username)
@@ -231,7 +237,7 @@ public class ChatClient
.url(url)
.build();
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
return response.isSuccessful();
}
@@ -239,7 +245,7 @@ public class ChatClient
public int getGc(String username) throws IOException
{
HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("chat")
.addPathSegment("gc")
.addQueryParameter("name", username)
@@ -249,7 +255,7 @@ public class ChatClient
.url(url)
.build();
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
if (!response.isSuccessful())
{
@@ -258,4 +264,150 @@ public class ChatClient
return Integer.parseInt(response.body().string());
}
}
public boolean submitLayout(String username, String layout) throws IOException
{
if (!testLayout(layout))
{
throw new IOException("Layout " + layout + " is not valid!");
}
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("chat")
.addPathSegment("layout")
.addQueryParameter("name", username)
.addQueryParameter("layout", layout)
.build();
Request request = new Request.Builder()
.post(RequestBody.create(null, new byte[0]))
.url(url)
.build();
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
return response.isSuccessful();
}
}
public String getLayout(String username) throws IOException
{
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("chat")
.addPathSegment("layout")
.addQueryParameter("name", username)
.build();
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
if (!response.isSuccessful())
{
throw new IOException("Unable to look up layout!");
}
final String layout = response.body().string();
if (!testLayout(layout))
{
throw new IOException("Layout " + layout + " is not valid!");
}
return layout;
}
}
public boolean testLayout(String layout)
{
return LAYOUT_VALIDATOR.test(layout);
}
public House[] getHosts(int world, String location) throws IOException
{
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("chat")
.addPathSegment("hosts")
.addQueryParameter("world", Integer.toString(world))
.addQueryParameter("location", location)
.build();
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
if (!response.isSuccessful())
{
throw new IOException("Unable to look up hosts!");
}
InputStream in = response.body().byteStream();
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), House[].class);
}
catch (JsonParseException ex)
{
throw new IOException(ex);
}
}
public boolean submitHost(int world, String location, House house) throws IOException
{
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("chat")
.addPathSegment("hosts")
.addQueryParameter("world", Integer.toString(world))
.addQueryParameter("location", location)
.addQueryParameter("owner", house.getOwner())
.addQueryParameter("guildedAltar", Boolean.toString(house.isGuildedAltarPresent()))
.addQueryParameter("occultAltar", Boolean.toString(house.isOccultAltarPresent()))
.addQueryParameter("spiritTree", Boolean.toString(house.isSpiritTreePresent()))
.addQueryParameter("fairyRing", Boolean.toString(house.isFairyRingPresent()))
.addQueryParameter("wildernessObelisk", Boolean.toString(house.isWildernessObeliskPresent()))
.addQueryParameter("repairStand", Boolean.toString(house.isRepairStandPresent()))
.addQueryParameter("combatDummy", Boolean.toString(house.isCombatDummyPresent()))
.build();
Request request = new Request.Builder()
.post(RequestBody.create(null, new byte[0]))
.url(url)
.build();
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
return response.isSuccessful();
}
}
public boolean removeHost(int world, String location, House house) throws IOException
{
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("chat")
.addPathSegment("hosts")
.addQueryParameter("world", Integer.toString(world))
.addQueryParameter("location", location)
.addQueryParameter("owner", house.getOwner())
.addQueryParameter("guildedAltar", Boolean.toString(house.isGuildedAltarPresent()))
.addQueryParameter("occultAltar", Boolean.toString(house.isOccultAltarPresent()))
.addQueryParameter("spiritTree", Boolean.toString(house.isSpiritTreePresent()))
.addQueryParameter("fairyRing", Boolean.toString(house.isFairyRingPresent()))
.addQueryParameter("wildernessObelisk", Boolean.toString(house.isWildernessObeliskPresent()))
.addQueryParameter("repairStand", Boolean.toString(house.isRepairStandPresent()))
.addQueryParameter("combatDummy", Boolean.toString(house.isCombatDummyPresent()))
.addQueryParameter("remove", Boolean.toString(true))
.build();
Request request = new Request.Builder()
.post(RequestBody.create(null, new byte[0]))
.url(url)
.build();
try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute())
{
return response.isSuccessful();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* Copyright (c) 2019, Spedwards <https://github.com/Spedwards>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -22,36 +22,26 @@
* (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;
package net.runelite.http.api.chat;
public class UserEntry
import lombok.Data;
import net.runelite.http.api.RuneLiteAPI;
@Data
public class House
{
private int id;
private String username;
private String owner;
private boolean guildedAltarPresent;
private boolean occultAltarPresent;
private boolean spiritTreePresent;
private boolean fairyRingPresent;
private boolean wildernessObeliskPresent;
private boolean repairStandPresent;
private boolean combatDummyPresent;
@Override
public String toString()
{
return "UserEntry{" + "id=" + id + ", username=" + username + '}';
return RuneLiteAPI.GSON.toJson(this);
}
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;
}
}
}

View File

@@ -102,7 +102,7 @@ public class XteaClient
public List<XteaKey> get() throws IOException
{
HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("xtea")
.build();
@@ -127,7 +127,7 @@ public class XteaClient
public XteaKey get(int region) throws IOException
{
HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder()
.addPathSegment("xtea")
.addPathSegment(Integer.toString(region))
.build();

View File

@@ -122,11 +122,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>3.10.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -26,8 +26,6 @@ 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;
@@ -45,7 +43,6 @@ import okhttp3.OkHttpClient;
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;
@@ -73,7 +70,7 @@ public class SpringBootWebApplication extends SpringBootServletInitializer
@Override
public void contextInitialized(ServletContextEvent sce)
{
log.info("RuneLite API started");
log.info("RuneLitePlus API started");
}
@Override
@@ -129,7 +126,7 @@ public class SpringBootWebApplication extends SpringBootServletInitializer
return getDataSource(dataSourceProperties);
}
@Bean(value = "runelite-cache", destroyMethod = "")
@Bean(value = "runelite-cache2", destroyMethod = "")
public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
@@ -148,7 +145,7 @@ public class SpringBootWebApplication extends SpringBootServletInitializer
}
@Bean("Runelite Cache SQL2O")
public Sql2o cacheSql2o(@Qualifier("runelite-cache") DataSource dataSource)
public Sql2o cacheSql2o(@Qualifier("runelite-cache2") DataSource dataSource)
{
return createSql2oFromDataSource(dataSource);
}
@@ -159,12 +156,6 @@ public class SpringBootWebApplication extends SpringBootServletInitializer
return createSql2oFromDataSource(dataSource);
}
@Bean
public MongoClient mongoClient(@Value("${mongo.host}") String host)
{
return MongoClients.create(host);
}
private static DataSource getDataSource(DataSourceProperties dataSourceProperties)
{
if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName()))

View File

@@ -1,279 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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 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,
AuthFilter auth,
RedisPool jedisPool
)
{
this.sql2o = sql2o;
this.oauthClientId = oauthClientId;
this.oauthClientSecret = oauthClientSecret;
this.oauthCallback = oauthCallback;
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(RuneLiteAPI.getVersion());
OAuth20Service service = new ServiceBuilder()
.apiKey(oauthClientId)
.apiSecret(oauthClientSecret)
.scope(SCOPE)
.callback(oauthCallback)
.state(gson.toJson(state))
.build(GoogleApi20.instance());
final Map<String, String> 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;
}
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);
}
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.io.IOException;
import net.runelite.http.service.account.beans.SessionEntry;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.runelite.http.api.RuneLiteAPI;
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;
@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);
try (Connection con = sql2o.open())
{
SessionEntry sessionEntry = con.createQuery("select user, uuid, created from sessions where uuid = :uuid")
.addParameter("uuid", uuid.toString())
.executeAndFetchFirst(SessionEntry.class);
if (sessionEntry == null)
{
response.sendError(401, "Access denied");
return null;
}
Instant now = Instant.now();
con.createQuery("update sessions set last_used = :last_used where uuid = :uuid")
.addParameter("last_used", Timestamp.from(now))
.addParameter("uuid", uuid.toString())
.executeUpdate();
sessionEntry.setLastUsed(now);
return sessionEntry;
}
}
}

View File

@@ -22,19 +22,24 @@
* (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;
package net.runelite.http.service.animation;
import java.time.Instant;
import lombok.AllArgsConstructor;
import lombok.Data;
import net.runelite.http.api.loottracker.LootRecordType;
@Data
class LootResult
@AllArgsConstructor
class AnimationCache
{
private int killId;
private Instant time;
private LootRecordType type;
private String eventId;
private int itemId;
private int itemQuantity;
private int npcid;
private int anim1;
private int anim2;
private int anim3;
private int anim4;
private int anim5;
private int anim6;
private int anim7;
private int anim8;
private int anim9;
private int anim10;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -22,13 +22,13 @@
* (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.examine;
package net.runelite.http.service.animation;
import static net.runelite.http.service.examine.ExamineType.ITEM;
import static net.runelite.http.service.examine.ExamineType.NPC;
import static net.runelite.http.service.examine.ExamineType.OBJECT;
import net.runelite.http.service.item.ItemEntry;
import net.runelite.http.service.item.ItemService;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.http.api.animation.AnimationKey;
import net.runelite.http.api.animation.AnimationRequest;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -38,59 +38,55 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/examine")
public class ExamineController
@RequestMapping("/animation")
public class AnimationController
{
private final ExamineService examineService;
private final ItemService itemService;
@Autowired
public ExamineController(ExamineService examineService, ItemService itemService)
private AnimationEndpoint animationService;
@RequestMapping(method = POST)
public void submit(@RequestBody AnimationRequest animationRequest)
{
this.examineService = examineService;
this.itemService = itemService;
animationService.submit(animationRequest);
}
@GetMapping("/npc/{id}")
public String getNpc(@PathVariable int id)
@GetMapping
public List<AnimationKey> get()
{
return examineService.get(NPC, id);
return animationService.get().stream()
.map(AnimationController::entryToKey)
.collect(Collectors.toList());
}
@GetMapping("/object/{id}")
public String getObject(@PathVariable int id)
@GetMapping("/{npcid}")
public AnimationKey getRegion(@PathVariable int npcid)
{
return examineService.get(OBJECT, id);
}
@GetMapping("/item/{id}")
public String getItem(@PathVariable int id)
{
// Tradeable item examine info is available from the Jagex item API
ItemEntry item = itemService.getItem(id);
if (item != null)
AnimationEntry animationEntry = animationService.getNPC(npcid);
if (animationEntry == null)
{
return item.getDescription();
throw new NotFoundException();
}
return examineService.get(ITEM, id);
return entryToKey(animationEntry);
}
@RequestMapping(path = "/npc/{id}", method = POST)
public void submitNpc(@PathVariable int id, @RequestBody String examine)
private static AnimationKey entryToKey(AnimationEntry xe)
{
examineService.insert(NPC, id, examine);
}
@RequestMapping(path = "/object/{id}", method = POST)
public void submitObject(@PathVariable int id, @RequestBody String examine)
{
examineService.insert(OBJECT, id, examine);
}
@RequestMapping(path = "/item/{id}", method = POST)
public void submitItem(@PathVariable int id, @RequestBody String examine)
{
examineService.insert(ITEM, id, examine);
AnimationKey animationKey = new AnimationKey();
animationKey.setNPCId(xe.getNPCId());
animationKey.setAnimations(new int[]
{
xe.getAnimations()[0],
xe.getAnimations()[1],
xe.getAnimations()[2],
xe.getAnimations()[3],
xe.getAnimations()[4],
xe.getAnimations()[5],
xe.getAnimations()[6],
xe.getAnimations()[7],
xe.getAnimations()[8],
xe.getAnimations()[9],
});
return animationKey;
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.animation;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.List;
import net.runelite.http.api.animation.AnimationKey;
import net.runelite.http.api.animation.AnimationRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
public class AnimationEndpoint
{
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `animation` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `npcid` int(11) NOT NULL,\n"
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " `rev` int(11) NOT NULL,\n"
+ " `anim1` int(11) NOT NULL,\n"
+ " `anim2` int(11),\n"
+ " `anim3` int(11),\n"
+ " `anim4` int(11),\n"
+ " `anim5` int(11),\n"
+ " `anim6` int(11),\n"
+ " `anim7` int(11),\n"
+ " `anim8` int(11),\n"
+ " `anim9` int(11),\n"
+ " `anim10` int(11),\n"
+ " PRIMARY KEY (`id`),\n"
+ " KEY `npcid` (`npcid`,`time`)\n"
+ ") ENGINE=InnoDB";
private final Sql2o sql2o;
private final Cache<Integer, AnimationCache> keyCache = CacheBuilder.newBuilder()
.maximumSize(1024)
.build();
@Autowired
public AnimationEndpoint(
@Qualifier("Runelite SQL2O") Sql2o sql2o
)
{
this.sql2o = sql2o;
try (Connection con = sql2o.beginTransaction())
{
con.createQuery(CREATE_SQL)
.executeUpdate();
}
}
private AnimationEntry findLatestAnimations(Connection con, int npcid)
{
return con.createQuery("select npcid, time, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation "
+ "where npcid = :npcid "
+ "order by time desc "
+ "limit 1")
.addParameter("npcid", npcid)
.executeAndFetchFirst(AnimationEntry.class);
}
public void submit(AnimationRequest animationRequest)
{
boolean cached = true;
for (AnimationKey key : animationRequest.getKeys())
{
int npcid = key.getNPCId();
int[] animations = key.getAnimations();
AnimationCache animationCache = keyCache.getIfPresent(npcid);
if (animationCache == null
|| animationCache.getAnim1() != animations[0]
|| animationCache.getAnim2() != animations[1]
|| animationCache.getAnim3() != animations[2]
|| animationCache.getAnim4() != animations[3]
|| animationCache.getAnim5() != animations[4]
|| animationCache.getAnim6() != animations[5]
|| animationCache.getAnim7() != animations[6]
|| animationCache.getAnim8() != animations[7]
|| animationCache.getAnim9() != animations[8]
|| animationCache.getAnim10() != animations[9])
{
cached = false;
keyCache.put(npcid, new AnimationCache(npcid, animations[0], animations[1], animations[2], animations[3], animations[4], animations[5], animations[6], animations[7], animations[8], animations[9]));
}
}
if (cached)
{
return;
}
try (Connection con = sql2o.beginTransaction())
{
Query query = null;
for (AnimationKey key : animationRequest.getKeys())
{
int npcid = key.getNPCId();
int[] animations = key.getAnimations();
AnimationEntry animationEntry = findLatestAnimations(con, npcid);
if (animations.length != 10)
{
throw new IllegalArgumentException("Key length must be 10");
}
// already have these?
if (animationEntry != null
&& animationEntry.getAnimations()[0] == animations[0]
&& animationEntry.getAnimations()[1] == animations[1]
&& animationEntry.getAnimations()[2] == animations[2]
&& animationEntry.getAnimations()[3] == animations[3]
&& animationEntry.getAnimations()[4] == animations[4]
&& animationEntry.getAnimations()[5] == animations[5]
&& animationEntry.getAnimations()[6] == animations[6]
&& animationEntry.getAnimations()[7] == animations[7]
&& animationEntry.getAnimations()[8] == animations[8]
&& animationEntry.getAnimations()[9] == animations[9])
{
continue;
}
if (query == null)
{
query = con.createQuery("insert into animation (npcid, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10) "
+ "values (:npcid, :rev, :anim1, :anim2, :anim3, :anim4, anim5, anim6, anim7, anim8, anim9, anim10)");
}
query.addParameter("npcid", npcid)
.addParameter("rev", animationRequest.getRevision())
.addParameter("anim1", animations[0])
.addParameter("anim2", animations[1])
.addParameter("anim3", animations[2])
.addParameter("anim4", animations[3])
.addParameter("anim5", animations[4])
.addParameter("anim6", animations[5])
.addParameter("anim7", animations[6])
.addParameter("anim8", animations[7])
.addParameter("anim9", animations[8])
.addParameter("anim10", animations[9])
.addToBatch();
}
if (query != null)
{
query.executeBatch();
con.commit(false);
}
}
}
public List<AnimationEntry> get()
{
try (Connection con = sql2o.open())
{
return con.createQuery(
"select t1.npcid, t2.time, t2.rev, t2.anim1, t2.anim2, t2.anim3, t2.anim4, t2.anim5, t2.anim6, t2.anim7, t2.anim8, t2.anim9, t2.anim10 from " +
"(select npcid,max(id) as id from animation group by npcid) t1 " +
"join animation t2 on t1.id = t2.id")
.executeAndFetch(AnimationEntry.class);
}
}
public AnimationEntry getNPC(int npcid)
{
try (Connection con = sql2o.open())
{
return con.createQuery("select npcid, time, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation "
+ "where npcid = :npcid order by time desc limit 1")
.addParameter("npcid", npcid)
.executeAndFetchFirst(AnimationEntry.class);
}
}
}

View File

@@ -22,36 +22,25 @@
* (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.examine;
package net.runelite.http.service.animation;
import java.time.Instant;
public class ExamineEntry
public class AnimationEntry
{
private ExamineType type;
private int id;
private int npcid;
private Instant time;
private int count;
private String text;
private int rev;
private int[] animations;
public ExamineType getType()
public int getNPCId()
{
return type;
return npcid;
}
public void setType(ExamineType type)
public void setNPCId(int npcid)
{
this.type = type;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
this.npcid = npcid;
}
public Instant getTime()
@@ -64,23 +53,23 @@ public class ExamineEntry
this.time = time;
}
public int getCount()
public int getRev()
{
return count;
return rev;
}
public void setCount(int count)
public void setRev(int rev)
{
this.count = count;
this.rev = rev;
}
public String getText()
public int[] getAnimations()
{
return text;
return animations;
}
public void setText(String text)
public void setAnimations(int[] animations)
{
this.text = text;
this.animations = animations;
}
}

View File

@@ -1,362 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
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.ModelDefinition;
import net.runelite.cache.definitions.NpcDefinition;
import net.runelite.cache.definitions.ObjectDefinition;
import net.runelite.cache.definitions.SpriteDefinition;
import net.runelite.cache.definitions.TextureDefinition;
import net.runelite.cache.definitions.loaders.ItemLoader;
import net.runelite.cache.definitions.loaders.ModelLoader;
import net.runelite.cache.definitions.loaders.NpcLoader;
import net.runelite.cache.definitions.loaders.ObjectLoader;
import net.runelite.cache.definitions.loaders.SpriteLoader;
import net.runelite.cache.definitions.loaders.TextureLoader;
import net.runelite.cache.definitions.providers.ItemProvider;
import net.runelite.cache.definitions.providers.ModelProvider;
import net.runelite.cache.definitions.providers.SpriteProvider;
import net.runelite.cache.definitions.providers.TextureProvider;
import net.runelite.cache.fs.ArchiveFiles;
import net.runelite.cache.fs.Container;
import net.runelite.cache.fs.FSFile;
import net.runelite.cache.item.ItemSpriteFactory;
import net.runelite.http.api.cache.Cache;
import net.runelite.http.api.cache.CacheArchive;
import net.runelite.http.api.cache.CacheIndex;
import net.runelite.http.service.cache.beans.ArchiveEntry;
import net.runelite.http.service.cache.beans.CacheEntry;
import net.runelite.http.service.cache.beans.IndexEntry;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cache")
@Slf4j
public class CacheController
{
@Autowired
private CacheService cacheService;
@GetMapping("/")
public List<Cache> listCaches()
{
return cacheService.listCaches().stream()
.map(entry -> new Cache(entry.getId(), entry.getRevision(), entry.getDate()))
.collect(Collectors.toList());
}
@GetMapping("{cacheId}")
public List<CacheIndex> listIndexes(@PathVariable int cacheId)
{
CacheEntry cache = cacheService.findCache(cacheId);
if (cache == null)
{
throw new NotFoundException();
}
List<IndexEntry> indexes = cacheService.findIndexesForCache(cache);
return indexes.stream()
.map(entry -> new CacheIndex(entry.getIndexId(), entry.getRevision()))
.collect(Collectors.toList());
}
@GetMapping("{cacheId}/{indexId}")
public List<CacheArchive> listArchives(@PathVariable int cacheId,
@PathVariable int indexId)
{
CacheEntry cache = cacheService.findCache(cacheId);
if (cache == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
List<ArchiveEntry> archives = cacheService.findArchivesForIndex(indexEntry);
return archives.stream()
.map(archive -> new CacheArchive(archive.getArchiveId(), archive.getNameHash(), archive.getRevision()))
.collect(Collectors.toList());
}
@GetMapping("{cacheId}/{indexId}/{archiveId}")
public CacheArchive getCacheArchive(@PathVariable int cacheId,
@PathVariable int indexId,
@PathVariable int archiveId)
{
CacheEntry cache = cacheService.findCache(cacheId);
if (cache == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, archiveId);
if (archiveEntry == null)
{
throw new NotFoundException();
}
return new CacheArchive(archiveEntry.getArchiveId(),
archiveEntry.getNameHash(), archiveEntry.getRevision());
}
@GetMapping("{cacheId}/{indexId}/{archiveId}/data")
public byte[] getArchiveData(
@PathVariable int cacheId,
@PathVariable int indexId,
@PathVariable int archiveId
)
{
CacheEntry cache = cacheService.findCache(cacheId);
if (cache == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, archiveId);
if (archiveEntry == null)
{
throw new NotFoundException();
}
return cacheService.getArchive(archiveEntry);
}
private ArchiveEntry findConfig(ConfigType config)
{
CacheEntry cache = cacheService.findMostRecent();
if (cache == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.CONFIGS.getNumber());
if (indexEntry == null)
{
throw new NotFoundException();
}
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, config.getId());
if (archiveEntry == null)
{
throw new NotFoundException();
}
return archiveEntry;
}
@GetMapping("item/{itemId}")
public ItemDefinition getItem(@PathVariable int itemId) throws IOException
{
ArchiveEntry archiveEntry = findConfig(ConfigType.ITEM);
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(itemId);
if (file == null)
{
throw new NotFoundException();
}
ItemDefinition itemdef = new ItemLoader().load(itemId, file.getContents());
return itemdef;
}
@GetMapping(path = "item/{itemId}/image", produces = "image/png")
public ResponseEntity<byte[]> getItemImage(
@PathVariable int itemId,
@RequestParam(defaultValue = "1") int quantity,
@RequestParam(defaultValue = "1") int border,
@RequestParam(defaultValue = "3153952") int shadowColor
) throws IOException
{
final CacheEntry cache = cacheService.findMostRecent();
ItemProvider itemProvider = new ItemProvider()
{
@Override
public ItemDefinition provide(int itemId)
{
try
{
return getItem(itemId);
}
catch (IOException ex)
{
log.warn(null, ex);
return null;
}
}
};
ModelProvider modelProvider = new ModelProvider()
{
@Override
public ModelDefinition provide(int modelId) throws IOException
{
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.MODELS.getNumber());
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, modelId);
byte[] archiveData = Container.decompress(cacheService.getArchive(archiveEntry), null).data;
return new ModelLoader().load(modelId, archiveData);
}
};
SpriteProvider spriteProvider = new SpriteProvider()
{
@Override
public SpriteDefinition provide(int spriteId, int frameId)
{
try
{
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.SPRITES.getNumber());
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, spriteId);
byte[] archiveData = Container.decompress(cacheService.getArchive(archiveEntry), null).data;
SpriteDefinition[] defs = new SpriteLoader().load(spriteId, archiveData);
return defs[frameId];
}
catch (Exception ex)
{
log.warn(null, ex);
return null;
}
}
};
TextureProvider textureProvider2 = new TextureProvider()
{
@Override
public TextureDefinition[] provide()
{
try
{
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.TEXTURES.getNumber());
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, 0);
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
TextureLoader loader = new TextureLoader();
TextureDefinition[] defs = new TextureDefinition[archiveFiles.getFiles().size()];
int i = 0;
for (FSFile file : archiveFiles.getFiles())
{
TextureDefinition def = loader.load(file.getFileId(), file.getContents());
defs[i++] = def;
}
return defs;
}
catch (Exception ex)
{
log.warn(null, ex);
return null;
}
}
};
BufferedImage itemImage = ItemSpriteFactory.createSprite(itemProvider, modelProvider, spriteProvider, textureProvider2,
itemId, quantity, border, shadowColor, false);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(itemImage, "png", bao);
return ResponseEntity.ok(bao.toByteArray());
}
@GetMapping("object/{objectId}")
public ObjectDefinition getObject(
@PathVariable int objectId
) throws IOException
{
ArchiveEntry archiveEntry = findConfig(ConfigType.OBJECT);
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(objectId);
if (file == null)
{
throw new NotFoundException();
}
ObjectDefinition objectdef = new ObjectLoader().load(objectId, file.getContents());
return objectdef;
}
@GetMapping("npc/{npcId}")
public NpcDefinition getNpc(
@PathVariable int npcId
) throws IOException
{
ArchiveEntry archiveEntry = findConfig(ConfigType.NPC);
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(npcId);
if (file == null)
{
throw new NotFoundException();
}
NpcDefinition npcdef = new NpcLoader().load(npcId, file.getContents());
return npcdef;
}
}

View File

@@ -1,123 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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<CacheEntry> 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<IndexEntry> 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<ArchiveEntry> 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<FileEntry> 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);
}
}

View File

@@ -1,254 +0,0 @@
/*
* Copyright (c) 2017-2018, Adam <Adam@sigterm.info>
* 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<FileEntry> 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<CacheEntry> 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<IndexEntry> 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<ArchiveEntry> findArchivesForIndex(IndexEntry indexEntry)
{
try (Connection con = sql2o.open())
{
CacheDAO cacheDao = new CacheDAO();
ResultSetIterable<ArchiveEntry> archiveEntries = cacheDao.findArchivesForIndex(con, indexEntry);
List<ArchiveEntry> 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<ItemDefinition> 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<ItemDefinition> result = new ArrayList<>(archiveFiles.getFiles().size());
for (FSFile file : archiveFiles.getFiles())
{
ItemDefinition itemDef = itemLoader.load(file.getFileId(), file.getContents());
result.add(itemDef);
}
return result;
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -24,12 +24,9 @@
*/
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.Task;
import com.google.common.base.Strings;
import net.runelite.http.api.chat.House;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -45,136 +42,62 @@ public class ChatController
private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]");
private static final int STRING_MAX_LENGTH = 50;
private final Cache<KillCountKey, Integer> 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)
@PostMapping("/layout")
public void submitLayout(@RequestParam String name, @RequestParam String layout)
{
if (kc <= 0)
if (Strings.isNullOrEmpty(layout))
{
return;
}
chatService.setKc(name, boss, kc);
killCountCache.put(new KillCountKey(name, boss), kc);
chatService.setLayout(name, layout);
}
@GetMapping("/kc")
public int getKc(@RequestParam String name, @RequestParam String boss)
@GetMapping("/layout")
public String getLayout(@RequestParam String name)
{
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)
String layout = chatService.getLayout(name);
if (layout == null)
{
throw new NotFoundException();
}
return kc;
return layout;
}
@PostMapping("/qp")
public void submitQp(@RequestParam String name, @RequestParam int qp)
@PostMapping("/hosts")
public void submitHost(@RequestParam int world, @RequestParam String location, @RequestParam String owner, @RequestParam boolean guildedAltar, @RequestParam boolean occultAltar, @RequestParam boolean spiritTree, @RequestParam boolean fairyRing, @RequestParam boolean wildernessObelisk, @RequestParam boolean repairStand, @RequestParam boolean combatDummy, @RequestParam(required = false, defaultValue = "false") boolean remove)
{
if (qp < 0)
if (!location.equals("Rimmington") && !location.equals("Yanille"))
{
return;
}
chatService.setQp(name, qp);
}
House house = new House();
house.setOwner(owner);
house.setGuildedAltarPresent(guildedAltar);
house.setOccultAltarPresent(occultAltar);
house.setSpiritTreePresent(spiritTree);
house.setFairyRingPresent(fairyRing);
house.setWildernessObeliskPresent(wildernessObelisk);
house.setRepairStandPresent(repairStand);
house.setCombatDummyPresent(combatDummy);
@GetMapping("/qp")
public int getQp(@RequestParam String name)
{
Integer kc = chatService.getQp(name);
if (kc == null)
if (remove)
{
throw new NotFoundException();
chatService.removeHost(world, location, house);
}
return kc;
}
@PostMapping("/gc")
public void submitGc(@RequestParam String name, @RequestParam int gc)
{
if (gc < 0)
else
{
return;
chatService.addHost(world, location, house);
}
chatService.setGc(name, gc);
}
@GetMapping("/gc")
public int getKc(@RequestParam String name)
@GetMapping("/hosts")
public House[] getHosts(@RequestParam int world, @RequestParam String location)
{
Integer kc = chatService.getGc(name);
if (kc == null)
{
throw new NotFoundException();
}
return kc;
}
@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 Task getTask(@RequestParam String name)
{
return chatService.getTask(name);
}
@PostMapping("/pb")
public void submitPb(@RequestParam String name, @RequestParam String boss, @RequestParam int pb)
{
if (pb < 0)
{
return;
}
chatService.setPb(name, boss, pb);
}
@GetMapping("/pb")
public int getPb(@RequestParam String name, @RequestParam String boss)
{
Integer pb = chatService.getPb(name, boss);
if (pb == null)
{
throw new NotFoundException();
}
return pb;
return chatService.getHosts(world, location);
}
}

View File

@@ -24,10 +24,11 @@
*/
package net.runelite.http.service.chat;
import com.google.common.collect.ImmutableMap;
import java.time.Duration;
import java.util.Map;
import net.runelite.http.api.chat.Task;
import java.util.List;
import net.runelite.http.api.chat.ChatClient;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.chat.House;
import net.runelite.http.service.util.redis.RedisPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -39,6 +40,8 @@ public class ChatService
private static final Duration EXPIRE = Duration.ofMinutes(2);
private final RedisPool jedisPool;
private final ChatClient chatClient = new ChatClient();
@Autowired
public ChatService(RedisPool jedisPool)
@@ -46,115 +49,72 @@ public class ChatService
this.jedisPool = jedisPool;
}
public Integer getKc(String name, String boss)
public String getLayout(String name)
{
String value;
try (Jedis jedis = jedisPool.getResource())
{
value = jedis.get("kc." + name + "." + boss);
value = jedis.get("layout." + name);
}
return value == null ? null : Integer.parseInt(value);
return value;
}
public void setKc(String name, String boss, int kc)
public void setLayout(String name, String layout)
{
try (Jedis jedis = jedisPool.getResource())
if (!chatClient.testLayout(layout))
{
jedis.setex("kc." + name + "." + boss, (int) EXPIRE.getSeconds(), Integer.toString(kc));
throw new IllegalArgumentException(layout);
}
}
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 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 Task getTask(String name)
{
Map<String, String> map;
try (Jedis jedis = jedisPool.getResource())
{
map = jedis.hgetAll("task." + name);
jedis.setex("layout." + name, (int) EXPIRE.getSeconds(), layout);
}
}
public void addHost(int world, String location, House house)
{
String houseJSON = house.toString();
String key = "hosts.w" + Integer.toString(world) + "." + location;
try (Jedis jedis = jedisPool.getResource())
{
jedis.rpush(key, houseJSON);
}
}
public House[] getHosts(int world, String location)
{
List<String> json;
String key = "hosts.w" + Integer.toString(world) + "." + location;
try (Jedis jedis = jedisPool.getResource())
{
json = jedis.lrange(key, 0, 25);
}
if (map.isEmpty())
if (json.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<String, String> taskMap = ImmutableMap.<String, String>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())
House[] hosts = new House[json.size()];
for (int i = 0; i < json.size(); i++)
{
jedis.hmset(key, taskMap);
jedis.expire(key, (int) EXPIRE.getSeconds());
hosts[i] = RuneLiteAPI.GSON.fromJson(json.get(i), House.class);
}
return hosts;
}
public Integer getPb(String name, String boss)
public void removeHost(int world, String location, House house)
{
String value;
try (Jedis jedis = jedisPool.getResource())
{
value = jedis.get("pb." + boss + "." + name);
}
return value == null ? null : Integer.parseInt(value);
}
String json = house.toString();
String key = "hosts.w" + Integer.toString(world) + "." + location;
public void setPb(String name, String boss, int pb)
{
try (Jedis jedis = jedisPool.getResource())
{
jedis.setex("pb." + boss + "." + name, (int) EXPIRE.getSeconds(), Integer.toString(pb));
jedis.lrem(key, 0, json);
}
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* 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.examine;
import java.sql.Timestamp;
import java.time.Instant;
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 ExamineService
{
private static final String CREATE_EXAMINE = "CREATE TABLE IF NOT EXISTS `examine` (\n"
+ " `type` enum('OBJECT','NPC','ITEM') NOT NULL,\n"
+ " `id` int(11) NOT NULL,\n"
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " `count` int(11) NOT NULL,\n"
+ " `text` tinytext NOT NULL,\n"
+ " UNIQUE KEY `type` (`type`,`id`,`text`(64))\n"
+ ") ENGINE=InnoDB";
private final Sql2o sql2o;
@Autowired
public ExamineService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
{
this.sql2o = sql2o;
try (Connection con = sql2o.open())
{
con.createQuery(CREATE_EXAMINE)
.executeUpdate();
}
}
public String get(ExamineType type, int id)
{
try (Connection con = sql2o.open())
{
ExamineEntry entry = con.createQuery("select text from examine where type = :type and id = :id "
+ "order by count desc limit 1")
.addParameter("type", type.toString())
.addParameter("id", id)
.executeAndFetchFirst(ExamineEntry.class);
if (entry != null)
{
return entry.getText();
}
}
return null;
}
public void insert(ExamineType type, int id, String examine)
{
try (Connection con = sql2o.open())
{
con.createQuery("insert into examine (type, id, time, count, text) values "
+ "(:type, :id, :time, :count, :text) on duplicate key update count = count + 1")
.addParameter("type", type.toString())
.addParameter("id", id)
.addParameter("time", Timestamp.from(Instant.now()))
.addParameter("count", 1)
.addParameter("text", examine)
.executeUpdate();
}
}
}

View File

@@ -1,112 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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 java.io.IOException;
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.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 FeedResult feedResult;
@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<FeedItem> items = new ArrayList<>();
try
{
items.addAll(blogService.getBlogPosts());
}
catch (IOException e)
{
log.warn(e.getMessage());
}
try
{
items.addAll(twitterService.getTweets());
}
catch (IOException e)
{
log.warn(e.getMessage());
}
try
{
items.addAll(osrsNewsService.getNews());
}
catch (IOException e)
{
log.warn(e.getMessage());
}
feedResult = new FeedResult(items);
}
@GetMapping
public ResponseEntity<FeedResult> getFeed()
{
if (feedResult == null)
{
return ResponseEntity.notFound()
.build();
}
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic())
.body(feedResult);
}
}

View File

@@ -1,129 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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.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.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
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 HttpUrl RSS_URL = HttpUrl.parse("https://runelite.net/atom.xml");
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
public List<FeedItem> getBlogPosts() throws IOException
{
Request request = new Request.Builder()
.url(RSS_URL)
.build();
try (Response response = RuneLiteAPI.CLIENT.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<FeedItem> 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());
}
}
}
}

View File

@@ -1,129 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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.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.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
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 HttpUrl RSS_URL = HttpUrl.parse("https://services.runescape.com/m=news/latest_news.rss?oldschool=true");
private static final SimpleDateFormat PUB_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy '00:00:00 GMT'", Locale.US);
public List<FeedItem> getNews() throws IOException
{
Request request = new Request.Builder()
.url(RSS_URL)
.build();
try (Response response = RuneLiteAPI.CLIENT.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<FeedItem> 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());
}
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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;
}

View File

@@ -1,178 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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.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.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 String token;
@Autowired
public TwitterService(
@Value("${runelite.twitter.consumerkey}") String consumerKey,
@Value("${runelite.twitter.secretkey}") String consumerSecret,
@Value("${runelite.twitter.listid}") String listId
)
{
this.credentials = consumerKey + ":" + consumerSecret;
this.listId = listId;
}
public List<FeedItem> getTweets() throws IOException
{
return getTweets(false);
}
private List<FeedItem> 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 = RuneLiteAPI.CLIENT.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<List<TwitterStatusesResponseItem>>()
{
}.getType();
List<TwitterStatusesResponseItem> statusesResponse = RuneLiteAPI.GSON
.fromJson(new InputStreamReader(in), listType);
List<FeedItem> 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());
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 = RuneLiteAPI.CLIENT.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), 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;
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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;
}

View File

@@ -1,111 +0,0 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.ge;
import java.io.IOException;
import java.util.Collection;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.runelite.http.api.ge.GrandExchangeTrade;
import net.runelite.http.service.account.AuthFilter;
import net.runelite.http.service.account.beans.SessionEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/ge")
public class GrandExchangeController
{
private final GrandExchangeService grandExchangeService;
private final AuthFilter authFilter;
@Autowired
public GrandExchangeController(GrandExchangeService grandExchangeService, AuthFilter authFilter)
{
this.grandExchangeService = grandExchangeService;
this.authFilter = authFilter;
}
@PostMapping
public void submit(HttpServletRequest request, HttpServletResponse response, @RequestBody GrandExchangeTrade grandExchangeTrade) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return;
}
grandExchangeService.add(session.getUser(), grandExchangeTrade);
}
@GetMapping
public Collection<GrandExchangeTrade> get(HttpServletRequest request, HttpServletResponse response,
@RequestParam(required = false, defaultValue = "1024") int limit,
@RequestParam(required = false, defaultValue = "0") int offset) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return null;
}
return grandExchangeService.get(session.getUser(), limit, offset).stream()
.map(GrandExchangeController::convert)
.collect(Collectors.toList());
}
private static GrandExchangeTrade convert(TradeEntry tradeEntry)
{
GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade();
grandExchangeTrade.setBuy(tradeEntry.getAction() == TradeAction.BUY);
grandExchangeTrade.setItemId(tradeEntry.getItem());
grandExchangeTrade.setQuantity(tradeEntry.getQuantity());
grandExchangeTrade.setPrice(tradeEntry.getPrice());
grandExchangeTrade.setTime(tradeEntry.getTime());
return grandExchangeTrade;
}
@DeleteMapping
public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return;
}
grandExchangeService.delete(session.getUser());
}
}

View File

@@ -1,113 +0,0 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.ge;
import java.util.Collection;
import net.runelite.http.api.ge.GrandExchangeTrade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Sql2o;
@Service
public class GrandExchangeService
{
private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS `ge_trades` (\n" +
" `id` int(11) NOT NULL AUTO_INCREMENT,\n" +
" `user` int(11) NOT NULL,\n" +
" `action` enum('BUY','SELL') NOT NULL,\n" +
" `item` int(11) NOT NULL,\n" +
" `quantity` int(11) NOT NULL,\n" +
" `price` int(11) NOT NULL,\n" +
" `time` timestamp NOT NULL DEFAULT current_timestamp(),\n" +
" PRIMARY KEY (`id`),\n" +
" KEY `user_time` (`user`, `time`),\n" +
" KEY `time` (`time`),\n" +
" CONSTRAINT `ge_trades_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`)\n" +
") ENGINE=InnoDB;";
private final Sql2o sql2o;
@Autowired
public GrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
{
this.sql2o = sql2o;
// Ensure necessary tables exist
try (Connection con = sql2o.open())
{
con.createQuery(CREATE_TABLE).executeUpdate();
}
}
public void add(int userId, GrandExchangeTrade grandExchangeTrade)
{
try (Connection con = sql2o.open())
{
con.createQuery("insert into ge_trades (user, action, item, quantity, price) values (:user," +
" :action, :item, :quantity, :price)")
.addParameter("user", userId)
.addParameter("action", grandExchangeTrade.isBuy() ? "BUY" : "SELL")
.addParameter("item", grandExchangeTrade.getItemId())
.addParameter("quantity", grandExchangeTrade.getQuantity())
.addParameter("price", grandExchangeTrade.getPrice())
.executeUpdate();
}
}
public Collection<TradeEntry> get(int userId, int limit, int offset)
{
try (Connection con = sql2o.open())
{
return con.createQuery("select id, user, action, item, quantity, price, time from ge_trades where user = :user limit :limit offset :offset")
.addParameter("user", userId)
.addParameter("limit", limit)
.addParameter("offset", offset)
.executeAndFetch(TradeEntry.class);
}
}
public void delete(int userId)
{
try (Connection con = sql2o.open())
{
con.createQuery("delete from ge_trades where user = :user")
.addParameter("user", userId)
.executeUpdate();
}
}
@Scheduled(fixedDelay = 60 * 60 * 1000)
public void expire()
{
try (Connection con = sql2o.open())
{
con.createQuery("delete from ge_trades where time < current_timestamp - interval 1 month")
.executeUpdate();
}
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -1,96 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.hiscore;
import java.util.concurrent.ExecutionException;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import net.runelite.http.api.hiscore.HiscoreResult;
import net.runelite.http.api.hiscore.HiscoreSkill;
import net.runelite.http.api.hiscore.SingleHiscoreSkillResult;
import net.runelite.http.api.hiscore.Skill;
import net.runelite.http.service.util.HiscoreEndpointEditor;
import net.runelite.http.service.xp.XpTrackerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hiscore")
public class HiscoreController
{
@Autowired
private HiscoreService hiscoreService;
@Autowired
private XpTrackerService xpTrackerService;
@GetMapping("/{endpoint}")
public HiscoreResult lookup(@PathVariable HiscoreEndpoint endpoint, @RequestParam String username) throws ExecutionException
{
HiscoreResult result = hiscoreService.lookupUsername(username, endpoint);
// Submit to xp tracker?
switch (endpoint)
{
case NORMAL:
case IRONMAN:
case ULTIMATE_IRONMAN:
case HARDCORE_IRONMAN:
xpTrackerService.update(username, result);
}
return result;
}
@GetMapping("/{endpoint}/{skillName}")
public SingleHiscoreSkillResult singleSkillLookup(@PathVariable HiscoreEndpoint endpoint, @PathVariable String skillName, @RequestParam String username) throws ExecutionException
{
HiscoreSkill skill = HiscoreSkill.valueOf(skillName.toUpperCase());
// RS api only supports looking up all stats
HiscoreResult result = hiscoreService.lookupUsername(username, endpoint);
// Find the skill to return
Skill requested = result.getSkill(skill);
SingleHiscoreSkillResult skillResult = new SingleHiscoreSkillResult();
skillResult.setPlayer(username);
skillResult.setSkillName(skillName);
skillResult.setSkill(requested);
return skillResult;
}
@InitBinder
public void initBinder(WebDataBinder binder)
{
binder.registerCustomEditor(HiscoreEndpoint.class, new HiscoreEndpointEditor());
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.hiscore;
import lombok.Value;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
@Value
class HiscoreKey
{
String username;
HiscoreEndpoint endpoint;
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.hiscore;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.hiscore.HiscoreClient;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import net.runelite.http.api.hiscore.HiscoreResult;
import okhttp3.HttpUrl;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class HiscoreService
{
private final HiscoreClient hiscoreClient = new HiscoreClient();
private final LoadingCache<HiscoreKey, HiscoreResult> hiscoreCache = CacheBuilder.newBuilder()
.maximumSize(128)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(
new CacheLoader<HiscoreKey, HiscoreResult>()
{
@Override
public HiscoreResult load(HiscoreKey key) throws IOException
{
return hiscoreClient.lookup(key.getUsername(), key.getEndpoint());
}
});
@VisibleForTesting
HiscoreResult lookupUsername(String username, HttpUrl httpUrl) throws IOException
{
return hiscoreClient.lookup(username, httpUrl);
}
public HiscoreResult lookupUsername(String username, HiscoreEndpoint endpoint) throws ExecutionException
{
return hiscoreCache.get(new HiscoreKey(username, endpoint));
}
}

View File

@@ -1,227 +0,0 @@
/*
* Copyright (c) 2017-2018, Adam <Adam@sigterm.info>
* 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.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import net.runelite.http.api.item.Item;
import net.runelite.http.api.item.ItemPrice;
import net.runelite.http.api.item.SearchResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/item")
public class ItemController
{
private static final String RUNELITE_CACHE = "RuneLite-Cache";
private static final int MAX_BATCH_LOOKUP = 1024;
private final Cache<Integer, Integer> cachedEmpty = CacheBuilder.newBuilder()
.maximumSize(1024L)
.build();
private final ItemService itemService;
private final Supplier<ItemPrice[]> memorizedPrices;
@Autowired
public ItemController(ItemService itemService)
{
this.itemService = itemService;
memorizedPrices = Suppliers.memoizeWithExpiration(() -> itemService.fetchPrices().stream()
.map(priceEntry ->
{
ItemPrice itemPrice = new ItemPrice();
itemPrice.setId(priceEntry.getItem());
itemPrice.setName(priceEntry.getName());
itemPrice.setPrice(priceEntry.getPrice());
itemPrice.setTime(priceEntry.getTime());
return itemPrice;
})
.toArray(ItemPrice[]::new), 30, TimeUnit.MINUTES);
}
@GetMapping("/{itemId}")
public Item getItem(HttpServletResponse response, @PathVariable int itemId)
{
ItemEntry item = itemService.getItem(itemId);
if (item != null)
{
return item.toItem();
}
itemService.queueItem(itemId);
return null;
}
@GetMapping(path = "/{itemId}/icon", produces = "image/gif")
public ResponseEntity<byte[]> getIcon(@PathVariable int itemId)
{
ItemEntry item = itemService.getItem(itemId);
if (item != null && item.getIcon() != null)
{
return ResponseEntity.ok(item.getIcon());
}
itemService.queueItem(itemId);
return ResponseEntity.notFound().build();
}
@GetMapping(path = "/{itemId}/icon/large", produces = "image/gif")
public ResponseEntity<byte[]> getIconLarge(HttpServletResponse response, @PathVariable int itemId)
{
ItemEntry item = itemService.getItem(itemId);
if (item != null && item.getIcon_large() != null)
{
return ResponseEntity.ok(item.getIcon_large());
}
itemService.queueItem(itemId);
return ResponseEntity.notFound().build();
}
@GetMapping("/{itemId}/price")
public ResponseEntity<ItemPrice> itemPrice(
@PathVariable int itemId,
@RequestParam(required = false) Instant time
)
{
if (cachedEmpty.getIfPresent(itemId) != null)
{
return ResponseEntity.notFound()
.header(RUNELITE_CACHE, "HIT")
.build();
}
Instant now = Instant.now();
if (time != null && time.isAfter(now))
{
time = now;
}
ItemEntry item = itemService.getItem(itemId);
if (item == null)
{
itemService.queueItem(itemId); // queue lookup
cachedEmpty.put(itemId, itemId); // cache empty
return ResponseEntity.notFound()
.header(RUNELITE_CACHE, "MISS")
.build();
}
PriceEntry priceEntry = itemService.getPrice(itemId, time);
if (time != null)
{
if (priceEntry == null)
{
// we maybe can't backfill this
return ResponseEntity.notFound()
.header(RUNELITE_CACHE, "MISS")
.build();
}
}
else if (priceEntry == null)
{
// Price is unknown
cachedEmpty.put(itemId, itemId);
return ResponseEntity.notFound()
.header(RUNELITE_CACHE, "MISS")
.build();
}
ItemPrice itemPrice = new ItemPrice();
itemPrice.setId(item.getId());
itemPrice.setName(item.getName());
itemPrice.setPrice(priceEntry.getPrice());
itemPrice.setTime(priceEntry.getTime());
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic())
.body(itemPrice);
}
@GetMapping("/search")
public SearchResult search(@RequestParam String query)
{
List<ItemEntry> result = itemService.search(query);
itemService.queueSearch(query);
SearchResult searchResult = new SearchResult();
searchResult.setItems(result.stream()
.map(ItemEntry::toItem)
.collect(Collectors.toList()));
return searchResult;
}
@GetMapping("/price")
public ItemPrice[] prices(@RequestParam("id") int[] itemIds)
{
if (itemIds.length > MAX_BATCH_LOOKUP)
{
itemIds = Arrays.copyOf(itemIds, MAX_BATCH_LOOKUP);
}
List<PriceEntry> prices = itemService.getPrices(itemIds);
return prices.stream()
.map(priceEntry ->
{
ItemPrice itemPrice = new ItemPrice();
itemPrice.setId(priceEntry.getItem());
itemPrice.setName(priceEntry.getName());
itemPrice.setPrice(priceEntry.getPrice());
itemPrice.setTime(priceEntry.getTime());
return itemPrice;
})
.toArray(ItemPrice[]::new);
}
@GetMapping("/prices")
public ResponseEntity<ItemPrice[]> prices()
{
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic())
.body(memorizedPrices.get());
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.Item;
import net.runelite.http.api.item.ItemType;
@Data
public class ItemEntry
{
private int id;
private String name;
private String description;
private ItemType type;
private byte[] icon;
private byte[] icon_large;
private Instant timestamp;
public Item toItem()
{
Item item = new Item();
item.setId(id);
item.setName(name);
item.setDescription(description);
item.setType(type);
return item;
}
}

View File

@@ -1,505 +0,0 @@
/*
* Copyright (c) 2017-2018, Adam <Adam@sigterm.info>
* 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.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
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.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
@Slf4j
public class ItemService
{
private static final String BASE = "https://services.runescape.com/m=itemdb_oldschool";
private static final HttpUrl RS_ITEM_URL = HttpUrl.parse(BASE + "/api/catalogue/detail.json");
private static final HttpUrl RS_PRICE_URL = HttpUrl.parse(BASE + "/api/graph");
private static final HttpUrl RS_SEARCH_URL = HttpUrl.parse(BASE + "/api/catalogue/items.json?category=1");
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"
+ " `icon` blob,\n"
+ " `icon_large` blob,\n"
+ " `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"
+ " PRIMARY KEY (`id`),\n"
+ " FULLTEXT idx_name (name)\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 static final int MAX_PENDING = 512;
private final Sql2o sql2o;
private final CacheService cacheService;
private final ConcurrentLinkedQueue<PendingLookup> pendingLookups = new ConcurrentLinkedQueue<PendingLookup>();
private int[] tradeableItems;
private final Random random = new Random();
@Autowired
public ItemService(@Qualifier("Runelite SQL2O") Sql2o sql2o,
CacheService cacheService)
{
this.sql2o = sql2o;
this.cacheService = cacheService;
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())
{
ItemEntry item = con.createQuery("select id, name, description, type, icon, icon_large from items where id = :id")
.addParameter("id", itemId)
.executeAndFetchFirst(ItemEntry.class);
return item;
}
}
private PriceEntry getPrice(Connection con, int itemId, Instant time)
{
if (time != null)
{
return con.createQuery("select item, name, price, time, fetched_time from prices t1 join items t2 on t1.item=t2.id where item = :item and time <= :time order by time desc limit 1")
.addParameter("item", itemId)
.addParameter("time", time.toString())
.executeAndFetchFirst(PriceEntry.class);
}
else
{
return con.createQuery("select item, name, price, time, fetched_time from prices t1 join items t2 on t1.item=t2.id where item = :item order by time desc limit 1")
.addParameter("item", itemId)
.executeAndFetchFirst(PriceEntry.class);
}
}
public PriceEntry getPrice(int itemId, Instant time)
{
try (Connection con = sql2o.open())
{
return getPrice(con, itemId, time);
}
}
public List<PriceEntry> getPrices(int... itemIds)
{
try (Connection con = sql2o.open())
{
Set<Integer> seen = new HashSet<>();
List<PriceEntry> priceEntries = new ArrayList<>(itemIds.length);
for (int itemId : itemIds)
{
if (seen.contains(itemId))
{
continue;
}
seen.add(itemId);
PriceEntry priceEntry = getPrice(con, itemId, null);
if (priceEntry == null)
{
continue;
}
priceEntries.add(priceEntry);
}
return priceEntries;
}
}
public List<ItemEntry> search(String search)
{
try (Connection con = sql2o.open())
{
return con.createQuery("select id, name, description, type, match (name) against (:search) as score from items "
+ "where match (name) against (:search) order by score desc limit 10")
.throwOnMappingFailure(false) // otherwise it tries to map 'score'
.addParameter("search", search)
.executeAndFetch(ItemEntry.class);
}
}
public ItemEntry fetchItem(int itemId)
{
try
{
RSItem rsItem = fetchRSItem(itemId);
byte[] icon = null, iconLarge = null;
try
{
icon = fetchImage(rsItem.getIcon());
}
catch (IOException ex)
{
log.warn("error fetching image", ex);
}
try
{
iconLarge = fetchImage(rsItem.getIcon_large());
}
catch (IOException ex)
{
log.warn("error fetching image", ex);
}
try (Connection con = sql2o.open())
{
con.createQuery("insert into items (id, name, description, type, icon, icon_large) values (:id,"
+ " :name, :description, :type, :icon, :icon_large) ON DUPLICATE KEY UPDATE name = :name,"
+ " description = :description, type = :type, icon = :icon, icon_large = :icon_large")
.addParameter("id", rsItem.getId())
.addParameter("name", rsItem.getName())
.addParameter("description", rsItem.getDescription())
.addParameter("type", rsItem.getType())
.addParameter("icon", icon)
.addParameter("icon_large", iconLarge)
.executeUpdate();
}
ItemEntry item = new ItemEntry();
item.setId(itemId);
item.setName(rsItem.getName());
item.setDescription(rsItem.getDescription());
item.setType(ItemType.of(rsItem.getType()));
item.setIcon(icon);
item.setIcon_large(iconLarge);
return item;
}
catch (IOException ex)
{
log.warn("unable to fetch item {}", itemId, ex);
return null;
}
}
public List<PriceEntry> fetchPrice(int itemId)
{
RSPrices rsprice;
try
{
rsprice = fetchRSPrices(itemId);
}
catch (IOException ex)
{
log.warn("unable to fetch price for item {}", itemId, ex);
return null;
}
try (Connection con = sql2o.beginTransaction())
{
List<PriceEntry> entries = new ArrayList<>();
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<Long, Integer> entry : rsprice.getDaily().entrySet())
{
long ts = entry.getKey(); // ms since epoch
int price = entry.getValue(); // gp
Instant time = Instant.ofEpochMilli(ts);
PriceEntry priceEntry = new PriceEntry();
priceEntry.setItem(itemId);
priceEntry.setPrice(price);
priceEntry.setTime(time);
priceEntry.setFetched_time(now);
entries.add(priceEntry);
query
.addParameter("item", itemId)
.addParameter("price", price)
.addParameter("time", time)
.addParameter("fetched_time", now)
.addToBatch();
}
query.executeBatch();
con.commit(false);
return entries;
}
}
public List<PriceEntry> fetchPrices()
{
try (Connection con = sql2o.beginTransaction())
{
Query query = con.createQuery("select t2.item, t3.name, t2.time, prices.price, prices.fetched_time 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");
return query.executeAndFetch(PriceEntry.class);
}
}
private RSItem fetchRSItem(int itemId) throws IOException
{
HttpUrl itemUrl = RS_ITEM_URL
.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 = RS_PRICE_URL
.newBuilder()
.addPathSegment(itemId + ".json")
.build();
Request request = new Request.Builder()
.url(priceUrl)
.build();
return fetchJson(request, RSPrices.class);
}
public RSSearch fetchRSSearch(String query) throws IOException
{
// rs api seems to require lowercase
query = query.toLowerCase();
HttpUrl searchUrl = RS_SEARCH_URL
.newBuilder()
.addQueryParameter("alpha", query)
.build();
Request request = new Request.Builder()
.url(searchUrl)
.build();
return fetchJson(request, RSSearch.class);
}
private void batchInsertItems(RSSearch search)
{
try (Connection con = sql2o.beginTransaction())
{
Query q = con.createQuery("insert into items (id, name, description, type) values (:id,"
+ " :name, :description, :type) ON DUPLICATE KEY UPDATE name = :name,"
+ " description = :description, type = :type");
for (RSItem rsItem : search.getItems())
{
q.addParameter("id", rsItem.getId())
.addParameter("name", rsItem.getName())
.addParameter("description", rsItem.getDescription())
.addParameter("type", rsItem.getType())
.addToBatch();
}
q.executeBatch();
con.commit(false);
}
}
private <T> T fetchJson(Request request, Class<T> clazz) throws IOException
{
try (Response response = RuneLiteAPI.CLIENT.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), clazz);
}
catch (JsonParseException ex)
{
throw new IOException(ex);
}
}
private byte[] fetchImage(String url) throws IOException
{
HttpUrl httpUrl = HttpUrl.parse(url);
Request request = new Request.Builder()
.url(httpUrl)
.build();
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
{
if (!response.isSuccessful())
{
throw new IOException("Unsuccessful http response: " + response);
}
return response.body().bytes();
}
}
public void queueSearch(String search)
{
if (pendingLookups.size() < MAX_PENDING)
{
pendingLookups.add(new PendingLookup(search, PendingLookup.Type.SEARCH));
}
else
{
log.debug("Dropping pending search for {}", search);
}
}
public void queueItem(int itemId)
{
if (pendingLookups.size() < MAX_PENDING)
{
pendingLookups.add(new PendingLookup(itemId, PendingLookup.Type.ITEM));
}
else
{
log.debug("Dropping pending item lookup for {}", itemId);
}
}
@Scheduled(fixedDelay = 5000)
public void check()
{
PendingLookup pendingLookup = pendingLookups.poll();
if (pendingLookup == null)
{
return;
}
switch (pendingLookup.getType())
{
case SEARCH:
try
{
RSSearch reSearch = fetchRSSearch(pendingLookup.getSearch());
batchInsertItems(reSearch);
}
catch (IOException ex)
{
log.warn("error while searching items", ex);
}
break;
case ITEM:
fetchItem(pendingLookup.getItemId());
break;
}
}
@Scheduled(fixedDelay = 20_000)
public void crawlPrices()
{
if (tradeableItems == null || tradeableItems.length == 0)
{
return;
}
int idx = random.nextInt(tradeableItems.length);
int id = tradeableItems[idx];
if (getItem(id) == null)
{
// This is a new item..
log.debug("Fetching new item {}", id);
queueItem(id);
return;
}
log.debug("Fetching price for {}", id);
fetchPrice(id);
}
@Scheduled(fixedDelay = 1_8000_000) // 30 minutes
public void reloadItems() throws IOException
{
List<ItemDefinition> 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(item -> item.isTradeable)
.mapToInt(item -> item.id)
.toArray();
log.debug("Loaded {} tradeable items", tradeableItems.length);
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.Value;
@Value
class PendingLookup
{
enum Type
{
SEARCH,
ITEM;
}
private final int itemId;
private final String search;
private final Type type;
public PendingLookup(int itemId, Type type)
{
this.itemId = itemId;
this.search = null;
this.type = type;
}
public PendingLookup(String search, Type type)
{
this.itemId = -1;
this.search = search;
this.type = type;
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
import net.runelite.http.api.item.Item;
import net.runelite.http.api.item.ItemType;
@Data
public class RSItem
{
private int id;
private String name;
private String description;
private String type;
private String icon;
private String icon_large;
public Item toItem()
{
Item item = new Item();
item.setId(id);
item.setName(name);
item.setType(ItemType.of(type));
item.setDescription(description);
return item;
}
}

View File

@@ -1,95 +0,0 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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 java.io.IOException;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.runelite.http.api.loottracker.LootRecord;
import net.runelite.http.service.account.AuthFilter;
import net.runelite.http.service.account.beans.SessionEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.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;
@RestController
@RequestMapping("/loottracker")
public class LootTrackerController
{
@Autowired
private LootTrackerService service;
@Autowired
private AuthFilter auth;
@RequestMapping(method = RequestMethod.POST)
public void storeLootRecord(HttpServletRequest request, HttpServletResponse response, @RequestBody LootRecord record) throws IOException
{
SessionEntry e = auth.handle(request, response);
if (e == null)
{
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
return;
}
service.store(record, e.getUser());
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
}
@GetMapping
public Collection<LootRecord> getLootRecords(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);
}
}

View File

@@ -1,196 +0,0 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.List;
import net.runelite.http.api.loottracker.GameItem;
import net.runelite.http.api.loottracker.LootRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
public class LootTrackerService
{
// Table for storing individual LootRecords
private static final String CREATE_KILLS = "CREATE TABLE IF NOT EXISTS `kills` (\n"
+ " `id` INT AUTO_INCREMENT UNIQUE,\n"
+ " `time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\n"
+ " `accountId` INT NOT NULL,\n"
+ " `type` enum('NPC', 'PLAYER', 'EVENT', 'UNKNOWN') NOT NULL,\n"
+ " `eventId` VARCHAR(255) NOT NULL,\n"
+ " PRIMARY KEY (id),\n"
+ " FOREIGN KEY (accountId) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,\n"
+ " INDEX idx_acc (accountId, time),"
+ " INDEX idx_time (time)"
+ ") ENGINE=InnoDB";
// Table for storing Items received as loot for individual LootRecords
private static final String CREATE_DROPS = "CREATE TABLE IF NOT EXISTS `drops` (\n"
+ " `killId` INT NOT NULL,\n"
+ " `itemId` INT NOT NULL,\n"
+ " `itemQuantity` INT NOT NULL,\n"
+ " FOREIGN KEY (killId) REFERENCES kills(id) ON DELETE CASCADE\n"
+ ") ENGINE=InnoDB";
// Queries for inserting kills
private static final String INSERT_KILL_QUERY = "INSERT INTO kills (accountId, type, eventId) VALUES (:accountId, :type, :eventId)";
private static final String INSERT_DROP_QUERY = "INSERT INTO drops (killId, itemId, itemQuantity) VALUES (LAST_INSERT_ID(), :itemId, :itemQuantity)";
private static final String SELECT_LOOT_QUERY = "SELECT killId,time,type,eventId,itemId,itemQuantity FROM kills JOIN drops ON drops.killId = kills.id WHERE accountId = :accountId ORDER BY TIME DESC LIMIT :limit OFFSET :offset";
private static final String DELETE_LOOT_ACCOUNT = "DELETE FROM kills WHERE accountId = :accountId";
private static final String DELETE_LOOT_ACCOUNT_EVENTID = "DELETE FROM kills WHERE accountId = :accountId AND eventId = :eventId";
private final Sql2o sql2o;
@Autowired
public LootTrackerService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
{
this.sql2o = sql2o;
// Ensure necessary tables exist
try (Connection con = sql2o.open())
{
con.createQuery(CREATE_KILLS).executeUpdate();
con.createQuery(CREATE_DROPS).executeUpdate();
}
}
/**
* Store LootRecord
*
* @param record LootRecord to store
* @param accountId runelite account id to tie data too
*/
public void store(LootRecord record, int accountId)
{
try (Connection con = sql2o.beginTransaction())
{
// Kill Entry Query
con.createQuery(INSERT_KILL_QUERY, true)
.addParameter("accountId", accountId)
.addParameter("type", record.getType())
.addParameter("eventId", record.getEventId())
.executeUpdate();
Query insertDrop = con.createQuery(INSERT_DROP_QUERY);
// Append all queries for inserting drops
for (GameItem drop : record.getDrops())
{
insertDrop
.addParameter("itemId", drop.getId())
.addParameter("itemQuantity", drop.getQty())
.addToBatch();
}
insertDrop.executeBatch();
con.commit(false);
}
}
public Collection<LootRecord> get(int accountId, int limit, int offset)
{
List<LootResult> 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<LootRecord> lootRecords = new ArrayList<>();
List<GameItem> gameItems = new ArrayList<>();
for (LootResult lootResult : lootResults)
{
if (current == null || current.getKillId() != lootResult.getKillId())
{
if (!gameItems.isEmpty())
{
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime());
lootRecords.add(lootRecord);
gameItems = new ArrayList<>();
}
current = lootResult;
}
GameItem gameItem = new GameItem(lootResult.getItemId(), lootResult.getItemQuantity());
gameItems.add(gameItem);
}
if (!gameItems.isEmpty())
{
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime());
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 = 15 * 60 * 1000)
public void expire()
{
try (Connection con = sql2o.open())
{
con.createQuery("delete from kills where time < current_timestamp() - interval 30 day")
.executeUpdate();
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2018, AeonLucid <https://github.com/AeonLucid>
* 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.osbuddy;
import java.time.Instant;
import lombok.Data;
@Data
class GrandExchangeEntry
{
private int item_id;
private int buy_average;
private int sell_average;
private int overall_average;
private Instant last_update;
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright (c) 2018, AeonLucid <https://github.com/AeonLucid>
* 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.osbuddy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/osb/ge")
public class OSBGrandExchangeController
{
private final OSBGrandExchangeService grandExchangeService;
@Autowired
public OSBGrandExchangeController(OSBGrandExchangeService grandExchangeService)
{
this.grandExchangeService = grandExchangeService;
}
@GetMapping
public ResponseEntity<GrandExchangeEntry> get(@RequestParam("itemId") int itemId) throws ExecutionException
{
GrandExchangeEntry grandExchangeEntry = grandExchangeService.get(itemId);
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic())
.body(grandExchangeEntry);
}
}

View File

@@ -1,119 +0,0 @@
/*
* Copyright (c) 2018, AeonLucid <https://github.com/AeonLucid>
* 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.osbuddy;
import java.io.IOException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.service.osbuddy.beans.OsbuddySummaryItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
@Slf4j
public class OSBGrandExchangeService
{
private static final String CREATE_GRAND_EXCHANGE_PRICES = "CREATE TABLE IF NOT EXISTS `osb_ge` (\n"
+ " `item_id` int(11) NOT NULL,\n"
+ " `buy_average` int(11) NOT NULL,\n"
+ " `sell_average` int(11) NOT NULL,\n"
+ " `overall_average` int(11) NOT NULL,\n"
+ " `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " PRIMARY KEY (`item_id`)\n"
+ ") ENGINE=InnoDB";
private static final OsbuddyClient CLIENT = new OsbuddyClient();
private final Sql2o sql2o;
@Autowired
public OSBGrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
{
this.sql2o = sql2o;
try (Connection con = sql2o.open())
{
con.createQuery(CREATE_GRAND_EXCHANGE_PRICES).executeUpdate();
}
}
public GrandExchangeEntry get(int itemId)
{
try (Connection con = sql2o.open())
{
return con.createQuery("SELECT item_id, buy_average, sell_average, overall_average, last_update"
+ " FROM osb_ge WHERE item_id = :itemId")
.addParameter("itemId", itemId)
.executeAndFetchFirst(GrandExchangeEntry.class);
}
}
@Scheduled(initialDelay = 1000 * 5, fixedDelay = 1000 * 60 * 30)
private void updateDatabase()
{
try
{
Map<Integer, OsbuddySummaryItem> summary = CLIENT.getSummary();
try (Connection con = sql2o.beginTransaction())
{
Instant updateTime = Instant.now();
Query query = con.createQuery("INSERT INTO osb_ge (item_id, buy_average, sell_average, overall_average,"
+ " last_update) VALUES (:itemId, :buyAverage, :sellAverage, :overallAverage, :lastUpdate)"
+ " ON DUPLICATE KEY UPDATE buy_average = VALUES(buy_average), sell_average = VALUES(sell_average),"
+ " overall_average = VALUES(overall_average), last_update = VALUES(last_update)");
for (Map.Entry<Integer, OsbuddySummaryItem> entry : summary.entrySet())
{
Integer itemId = entry.getKey();
OsbuddySummaryItem item = entry.getValue();
query
.addParameter("itemId", itemId)
.addParameter("buyAverage", item.getBuy_average())
.addParameter("sellAverage", item.getSell_average())
.addParameter("overallAverage", item.getOverall_average())
.addParameter("lastUpdate", Timestamp.from(updateTime))
.addToBatch();
}
query.executeBatch();
con.commit(false);
}
}
catch (IOException e)
{
log.warn("Error while updating the osb grand exchange table", e);
}
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright (c) 2018, AeonLucid <https://github.com/AeonLucid>
* 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.osbuddy;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.service.osbuddy.beans.OsbuddySummaryItem;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
public class OsbuddyClient
{
private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36";
public Map<Integer, OsbuddySummaryItem> getSummary() throws IOException
{
HttpUrl httpUrl = new HttpUrl.Builder()
.scheme("https")
.host("rsbuddy.com")
.addPathSegment("exchange")
.addPathSegment("summary.json")
.build();
Request request = new Request.Builder()
.url(httpUrl)
.header("User-Agent", USER_AGENT)
.build();
try (Response responseOk = RuneLiteAPI.CLIENT.newCall(request).execute())
{
if (!responseOk.isSuccessful())
{
throw new IOException("Error retrieving summary from OSBuddy: " + responseOk.message());
}
Type type = new TypeToken<Map<Integer, OsbuddySummaryItem>>()
{
}.getType();
return RuneLiteAPI.GSON.fromJson(responseOk.body().string(), type);
}
catch (JsonSyntaxException ex)
{
throw new IOException(ex);
}
}
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (c) 2018, AeonLucid <https://github.com/AeonLucid>
* 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.osbuddy.beans;
import lombok.Data;
@Data
public class OsbuddySummaryItem
{
private int id;
private String name;
private boolean members;
private int sp;
private int buy_average;
private int sell_average;
private int overall_average;
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.sprite;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/sprite")
public class SpriteController
{
@Autowired
private SpriteService spriteService;
private final LoadingCache<Integer, byte[]> spriteCache = CacheBuilder.newBuilder()
.maximumSize(1024L)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<Integer, byte[]>()
{
@Override
public byte[] load(Integer key) throws Exception
{
byte[] data = spriteService.getImagePng(key >>> 16, key & 0xffff);
return data != null ? data : new byte[0];
}
});
@GetMapping(produces = "image/png")
public ResponseEntity<byte[]> getSprite(
@RequestParam int spriteId,
@RequestParam(defaultValue = "0") int frameId
) throws IOException
{
byte[] data = spriteCache.getUnchecked(spriteId << 16 | frameId);
if (data == null || data.length == 0)
{
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(data);
}
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.sprite;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import net.runelite.cache.IndexType;
import net.runelite.cache.definitions.SpriteDefinition;
import net.runelite.cache.definitions.loaders.SpriteLoader;
import net.runelite.cache.fs.ArchiveFiles;
import net.runelite.cache.fs.FSFile;
import net.runelite.http.service.cache.CacheService;
import net.runelite.http.service.cache.beans.ArchiveEntry;
import net.runelite.http.service.cache.beans.CacheEntry;
import net.runelite.http.service.cache.beans.IndexEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SpriteService
{
@Autowired
private CacheService cacheService;
public SpriteDefinition getSprite(int spriteId, int frameId) throws IOException
{
CacheEntry cache = cacheService.findMostRecent();
if (cache == null)
{
return null;
}
IndexEntry index = cacheService.findIndexForCache(cache, IndexType.SPRITES.getNumber());
if (index == null)
{
return null;
}
ArchiveEntry archive = cacheService.findArchiveForIndex(index, spriteId);
if (archive == null)
{
return null;
}
ArchiveFiles files = cacheService.getArchiveFiles(archive);
if (files == null)
{
return null;
}
FSFile file = files.getFiles().get(0);
byte[] contents = file.getContents();
SpriteDefinition[] sprite = new SpriteLoader().load(archive.getArchiveId(), contents);
if (frameId < 0 || frameId >= sprite.length)
{
return null;
}
return sprite[frameId];
}
public BufferedImage getImage(int spriteId, int frameId) throws IOException
{
SpriteDefinition sprite = getSprite(spriteId, frameId);
if (sprite == null)
{
return null;
}
BufferedImage bufferedImage = getSpriteImage(sprite);
return bufferedImage;
}
public byte[] getImagePng(int spriteId, int frameId) throws IOException
{
BufferedImage image = getImage(spriteId, frameId);
if (image == null)
{
return null;
}
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(image, "png", bao);
return bao.toByteArray();
}
private BufferedImage getSpriteImage(SpriteDefinition sprite)
{
BufferedImage image = new BufferedImage(sprite.getWidth(), sprite.getHeight(), BufferedImage.TYPE_INT_ARGB);
image.setRGB(0, 0, sprite.getWidth(), sprite.getHeight(), sprite.getPixels(), 0, sprite.getWidth());
return image;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.util;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class CacheControlFilter implements ResponseBodyAdvice<Object>
{
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType)
{
return true;
}
@Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response
)
{
if (!response.getHeaders().containsKey("Cache-Control"))
{
response.getHeaders().add("Cache-Control", "no-cache, no-store, must-revalidate");
response.getHeaders().add("pragma", "no-cache");
}
return body;
}
}

View File

@@ -35,4 +35,4 @@ public class InternalServerErrorException extends RuntimeException
{
super(message);
}
}
}

View File

@@ -32,4 +32,4 @@ import org.springframework.web.bind.annotation.ResponseStatus;
public class NotFoundException extends RuntimeException
{
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.worlds;
import net.runelite.http.api.worlds.WorldType;
enum ServiceWorldType
{
MEMBERS(WorldType.MEMBERS, 1),
PVP(WorldType.PVP, 1 << 2),
BOUNTY(WorldType.BOUNTY, 1 << 5),
SKILL_TOTAL(WorldType.SKILL_TOTAL, 1 << 7),
HIGH_RISK(WorldType.HIGH_RISK, 1 << 10),
LAST_MAN_STANDING(WorldType.LAST_MAN_STANDING, 1 << 14),
TOURNAMENT(WorldType.TOURNAMENT, 1 << 25),
DEADMAN_TOURNAMENT(WorldType.DEADMAN_TOURNAMENT, 1 << 26),
DEADMAN(WorldType.DEADMAN, 1 << 29),
SEASONAL_DEADMAN(WorldType.SEASONAL_DEADMAN, 1 << 30);
private final WorldType apiType;
private final int mask;
ServiceWorldType(WorldType apiType, int mask)
{
this.apiType = apiType;
this.mask = mask;
}
public WorldType getApiType()
{
return apiType;
}
public int getMask()
{
return mask;
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.worlds;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.worlds.WorldResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/worlds")
@Slf4j
public class WorldController
{
@Autowired
private WorldsService worldsService;
private WorldResult worldResult;
@GetMapping
public ResponseEntity<WorldResult> listWorlds() throws IOException
{
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic())
.body(worldResult);
}
@Scheduled(fixedDelay = 60_000L)
public void refreshWorlds() throws IOException
{
worldResult = worldsService.getWorlds();
}
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.worlds;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.worlds.World;
import net.runelite.http.api.worlds.WorldResult;
import net.runelite.http.api.worlds.WorldType;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.stereotype.Service;
@Service
public class WorldsService
{
private static final HttpUrl WORLD_URL = HttpUrl.parse("http://www.runescape.com/g=oldscape/slr.ws?order=LPWM");
private HttpUrl url = WORLD_URL;
public WorldResult getWorlds() throws IOException
{
Request okrequest = new Request.Builder()
.url(url)
.build();
byte[] b;
try (Response okresponse = RuneLiteAPI.CLIENT.newCall(okrequest).execute())
{
b = okresponse.body().bytes();
}
List<World> worlds = new ArrayList<>();
ByteBuffer buf = ByteBuffer.wrap(b);
int length = buf.getInt();
buf.limit(length + 4);
int num = buf.getShort() & 0xFFFF;
for (int i = 0; i < num; ++i)
{
final World.WorldBuilder worldBuilder = World.builder()
.id(buf.getShort() & 0xFFFF)
.types(getTypes(buf.getInt()))
.address(readString(buf))
.activity(readString(buf))
.location(buf.get() & 0xFF)
.players(buf.getShort() & 0xFFFF);
worlds.add(worldBuilder.build());
}
WorldResult result = new WorldResult();
result.setWorlds(worlds);
return result;
}
private static EnumSet<WorldType> getTypes(int mask)
{
EnumSet<WorldType> types = EnumSet.noneOf(WorldType.class);
for (ServiceWorldType type : ServiceWorldType.values())
{
if ((mask & type.getMask()) != 0)
{
types.add(type.getApiType());
}
}
return types;
}
private static String readString(ByteBuffer buf)
{
byte b;
StringBuilder sb = new StringBuilder();
for (;;)
{
b = buf.get();
if (b == 0)
{
break;
}
sb.append((char) b);
}
return sb.toString();
}
public HttpUrl getUrl()
{
return url;
}
public void setUrl(HttpUrl url)
{
this.url = url;
}
}

View File

@@ -1,92 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.xp;
import net.runelite.http.api.hiscore.HiscoreResult;
import net.runelite.http.api.xp.XpData;
import net.runelite.http.service.xp.beans.XpEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface XpMapper
{
XpMapper INSTANCE = Mappers.getMapper(XpMapper.class);
XpData xpEntityToXpData(XpEntity xpEntity);
@Mapping(target = "time", ignore = true)
@Mapping(source = "attack.experience", target = "attack_xp")
@Mapping(source = "defence.experience", target = "defence_xp")
@Mapping(source = "strength.experience", target = "strength_xp")
@Mapping(source = "hitpoints.experience", target = "hitpoints_xp")
@Mapping(source = "ranged.experience", target = "ranged_xp")
@Mapping(source = "prayer.experience", target = "prayer_xp")
@Mapping(source = "magic.experience", target = "magic_xp")
@Mapping(source = "cooking.experience", target = "cooking_xp")
@Mapping(source = "woodcutting.experience", target = "woodcutting_xp")
@Mapping(source = "fletching.experience", target = "fletching_xp")
@Mapping(source = "fishing.experience", target = "fishing_xp")
@Mapping(source = "firemaking.experience", target = "firemaking_xp")
@Mapping(source = "crafting.experience", target = "crafting_xp")
@Mapping(source = "smithing.experience", target = "smithing_xp")
@Mapping(source = "mining.experience", target = "mining_xp")
@Mapping(source = "herblore.experience", target = "herblore_xp")
@Mapping(source = "agility.experience", target = "agility_xp")
@Mapping(source = "thieving.experience", target = "thieving_xp")
@Mapping(source = "slayer.experience", target = "slayer_xp")
@Mapping(source = "farming.experience", target = "farming_xp")
@Mapping(source = "runecraft.experience", target = "runecraft_xp")
@Mapping(source = "hunter.experience", target = "hunter_xp")
@Mapping(source = "construction.experience", target = "construction_xp")
@Mapping(source = "overall.rank", target = "overall_rank")
@Mapping(source = "attack.rank", target = "attack_rank")
@Mapping(source = "defence.rank", target = "defence_rank")
@Mapping(source = "strength.rank", target = "strength_rank")
@Mapping(source = "hitpoints.rank", target = "hitpoints_rank")
@Mapping(source = "ranged.rank", target = "ranged_rank")
@Mapping(source = "prayer.rank", target = "prayer_rank")
@Mapping(source = "magic.rank", target = "magic_rank")
@Mapping(source = "cooking.rank", target = "cooking_rank")
@Mapping(source = "woodcutting.rank", target = "woodcutting_rank")
@Mapping(source = "fletching.rank", target = "fletching_rank")
@Mapping(source = "fishing.rank", target = "fishing_rank")
@Mapping(source = "firemaking.rank", target = "firemaking_rank")
@Mapping(source = "crafting.rank", target = "crafting_rank")
@Mapping(source = "smithing.rank", target = "smithing_rank")
@Mapping(source = "mining.rank", target = "mining_rank")
@Mapping(source = "herblore.rank", target = "herblore_rank")
@Mapping(source = "agility.rank", target = "agility_rank")
@Mapping(source = "thieving.rank", target = "thieving_rank")
@Mapping(source = "slayer.rank", target = "slayer_rank")
@Mapping(source = "farming.rank", target = "farming_rank")
@Mapping(source = "runecraft.rank", target = "runecraft_rank")
@Mapping(source = "hunter.rank", target = "hunter_rank")
@Mapping(source = "construction.rank", target = "construction_rank")
XpData hiscoreResultToXpData(HiscoreResult hiscoreResult);
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.xp;
import java.time.Instant;
import net.runelite.http.api.xp.XpData;
import net.runelite.http.service.xp.beans.XpEntity;
import org.springframework.beans.factory.annotation.Autowired;
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;
@RestController
@RequestMapping("/xp")
public class XpTrackerController
{
@Autowired
private XpTrackerService xpTrackerService;
@GetMapping("/update")
public void update(@RequestParam String username)
{
xpTrackerService.tryUpdate(username);
}
@GetMapping("/get")
public XpData get(@RequestParam String username, @RequestParam(required = false) Instant time)
{
if (time == null)
{
time = Instant.now();
}
XpEntity xpEntity = xpTrackerService.findXpAtTime(username, time);
return XpMapper.INSTANCE.xpEntityToXpData(xpEntity);
}
}

View File

@@ -1,307 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.xp;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.ExecutionException;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import net.runelite.http.api.hiscore.HiscoreResult;
import net.runelite.http.api.xp.XpData;
import net.runelite.http.service.hiscore.HiscoreService;
import net.runelite.http.service.xp.beans.PlayerEntity;
import net.runelite.http.service.xp.beans.XpEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Sql2o;
@Service
@Slf4j
public class XpTrackerService
{
private static final int QUEUE_LIMIT = 32768;
private static final int BLOOMFILTER_EXPECTED_INSERTIONS = 100_000;
@Autowired
@Qualifier("Runelite XP Tracker SQL2O")
private Sql2o sql2o;
@Autowired
private HiscoreService hiscoreService;
private final Queue<String> usernameUpdateQueue = new ArrayDeque<>();
private BloomFilter<String> usernameFilter = createFilter();
public void update(String username) throws ExecutionException
{
HiscoreResult hiscoreResult = hiscoreService.lookupUsername(username, HiscoreEndpoint.NORMAL);
update(username, hiscoreResult);
}
public void tryUpdate(String username)
{
if (usernameFilter.mightContain(username))
{
return;
}
try (Connection con = sql2o.open())
{
PlayerEntity playerEntity = findOrCreatePlayer(con, username);
Duration frequency = updateFrequency(playerEntity);
Instant now = Instant.now();
Duration timeSinceLastUpdate = Duration.between(playerEntity.getLast_updated(), now);
if (timeSinceLastUpdate.toMillis() < frequency.toMillis())
{
log.debug("User {} updated too recently", username);
usernameFilter.put(username);
return;
}
synchronized (usernameUpdateQueue)
{
if (usernameUpdateQueue.size() >= QUEUE_LIMIT)
{
log.warn("Username update queue is full ({})", QUEUE_LIMIT);
return;
}
usernameUpdateQueue.add(username);
}
}
usernameFilter.put(username);
}
public void update(String username, HiscoreResult hiscoreResult)
{
try (Connection con = sql2o.open())
{
PlayerEntity playerEntity = findOrCreatePlayer(con, username);
Instant now = Instant.now();
XpEntity currentXp = findXpAtTime(con, username, now);
if (currentXp != null)
{
XpData hiscoreData = XpMapper.INSTANCE.hiscoreResultToXpData(hiscoreResult);
XpData existingData = XpMapper.INSTANCE.xpEntityToXpData(currentXp);
if (hiscoreData.equals(existingData))
{
log.debug("Hiscore for {} already up to date", username);
return;
}
}
con.createQuery("insert into xp (player,attack_xp,defence_xp,strength_xp,hitpoints_xp,ranged_xp,prayer_xp,magic_xp,cooking_xp,woodcutting_xp,"
+ "fletching_xp,fishing_xp,firemaking_xp,crafting_xp,smithing_xp,mining_xp,herblore_xp,agility_xp,thieving_xp,slayer_xp,farming_xp,"
+ "runecraft_xp,hunter_xp,construction_xp,attack_rank,defence_rank,strength_rank,hitpoints_rank,ranged_rank,prayer_rank,magic_rank,"
+ "cooking_rank,woodcutting_rank,fletching_rank,fishing_rank,firemaking_rank,crafting_rank,smithing_rank,mining_rank,herblore_rank,"
+ "agility_rank,thieving_rank,slayer_rank,farming_rank,runecraft_rank,hunter_rank,construction_rank,overall_rank) values (:player,:attack_xp,:defence_xp,"
+ ":strength_xp,:hitpoints_xp,:ranged_xp,:prayer_xp,:magic_xp,:cooking_xp,:woodcutting_xp,:fletching_xp,:fishing_xp,:firemaking_xp,"
+ ":crafting_xp,:smithing_xp,:mining_xp,:herblore_xp,:agility_xp,:thieving_xp,:slayer_xp,:farming_xp,:runecraft_xp,:hunter_xp,"
+ ":construction_xp,:attack_rank,:defence_rank,:strength_rank,:hitpoints_rank,:ranged_rank,:prayer_rank,:magic_rank,:cooking_rank,"
+ ":woodcutting_rank,:fletching_rank,:fishing_rank,:firemaking_rank,:crafting_rank,:smithing_rank,:mining_rank,:herblore_rank,"
+ ":agility_rank,:thieving_rank,:slayer_rank,:farming_rank,:runecraft_rank,:hunter_rank,:construction_rank,:overall_rank)")
.addParameter("player", playerEntity.getId())
.addParameter("attack_xp", hiscoreResult.getAttack().getExperience())
.addParameter("defence_xp", hiscoreResult.getDefence().getExperience())
.addParameter("strength_xp", hiscoreResult.getStrength().getExperience())
.addParameter("hitpoints_xp", hiscoreResult.getHitpoints().getExperience())
.addParameter("ranged_xp", hiscoreResult.getRanged().getExperience())
.addParameter("prayer_xp", hiscoreResult.getPrayer().getExperience())
.addParameter("magic_xp", hiscoreResult.getMagic().getExperience())
.addParameter("cooking_xp", hiscoreResult.getCooking().getExperience())
.addParameter("woodcutting_xp", hiscoreResult.getWoodcutting().getExperience())
.addParameter("fletching_xp", hiscoreResult.getFletching().getExperience())
.addParameter("fishing_xp", hiscoreResult.getFishing().getExperience())
.addParameter("firemaking_xp", hiscoreResult.getFiremaking().getExperience())
.addParameter("crafting_xp", hiscoreResult.getCrafting().getExperience())
.addParameter("smithing_xp", hiscoreResult.getSmithing().getExperience())
.addParameter("mining_xp", hiscoreResult.getMining().getExperience())
.addParameter("herblore_xp", hiscoreResult.getHerblore().getExperience())
.addParameter("agility_xp", hiscoreResult.getAgility().getExperience())
.addParameter("thieving_xp", hiscoreResult.getThieving().getExperience())
.addParameter("slayer_xp", hiscoreResult.getSlayer().getExperience())
.addParameter("farming_xp", hiscoreResult.getFarming().getExperience())
.addParameter("runecraft_xp", hiscoreResult.getRunecraft().getExperience())
.addParameter("hunter_xp", hiscoreResult.getHunter().getExperience())
.addParameter("construction_xp", hiscoreResult.getConstruction().getExperience())
.addParameter("attack_rank", hiscoreResult.getAttack().getRank())
.addParameter("defence_rank", hiscoreResult.getDefence().getRank())
.addParameter("strength_rank", hiscoreResult.getStrength().getRank())
.addParameter("hitpoints_rank", hiscoreResult.getHitpoints().getRank())
.addParameter("ranged_rank", hiscoreResult.getRanged().getRank())
.addParameter("prayer_rank", hiscoreResult.getPrayer().getRank())
.addParameter("magic_rank", hiscoreResult.getMagic().getRank())
.addParameter("cooking_rank", hiscoreResult.getCooking().getRank())
.addParameter("woodcutting_rank", hiscoreResult.getWoodcutting().getRank())
.addParameter("fletching_rank", hiscoreResult.getFletching().getRank())
.addParameter("fishing_rank", hiscoreResult.getFishing().getRank())
.addParameter("firemaking_rank", hiscoreResult.getFiremaking().getRank())
.addParameter("crafting_rank", hiscoreResult.getCrafting().getRank())
.addParameter("smithing_rank", hiscoreResult.getSmithing().getRank())
.addParameter("mining_rank", hiscoreResult.getMining().getRank())
.addParameter("herblore_rank", hiscoreResult.getHerblore().getRank())
.addParameter("agility_rank", hiscoreResult.getAgility().getRank())
.addParameter("thieving_rank", hiscoreResult.getThieving().getRank())
.addParameter("slayer_rank", hiscoreResult.getSlayer().getRank())
.addParameter("farming_rank", hiscoreResult.getFarming().getRank())
.addParameter("runecraft_rank", hiscoreResult.getRunecraft().getRank())
.addParameter("hunter_rank", hiscoreResult.getHunter().getRank())
.addParameter("construction_rank", hiscoreResult.getConstruction().getRank())
.addParameter("overall_rank", hiscoreResult.getOverall().getRank())
.executeUpdate();
con.createQuery("update player set rank = :rank, last_updated = CURRENT_TIMESTAMP where id = :id")
.addParameter("id", playerEntity.getId())
.addParameter("rank", hiscoreResult.getOverall().getRank())
.executeUpdate();
}
}
private synchronized PlayerEntity findOrCreatePlayer(Connection con, String username)
{
PlayerEntity playerEntity = con.createQuery("select * from player where name = :name")
.addParameter("name", username)
.executeAndFetchFirst(PlayerEntity.class);
if (playerEntity != null)
{
return playerEntity;
}
Instant now = Instant.now();
int id = con.createQuery("insert into player (name, tracked_since) values (:name, :tracked_since)")
.addParameter("name", username)
.addParameter("tracked_since", now)
.executeUpdate()
.getKey(int.class);
playerEntity = new PlayerEntity();
playerEntity.setId(id);
playerEntity.setName(username);
playerEntity.setTracked_since(now);
playerEntity.setLast_updated(now);
return playerEntity;
}
private XpEntity findXpAtTime(Connection con, String username, Instant time)
{
return con.createQuery("select * from xp join player on player.id=xp.player where player.name = :username and time <= :time order by time desc limit 1")
.throwOnMappingFailure(false)
.addParameter("username", username)
.addParameter("time", time)
.executeAndFetchFirst(XpEntity.class);
}
public XpEntity findXpAtTime(String username, Instant time)
{
try (Connection con = sql2o.open())
{
return findXpAtTime(con, username, time);
}
}
@Scheduled(fixedDelay = 1000)
public void update() throws ExecutionException
{
String next;
synchronized (usernameUpdateQueue)
{
next = usernameUpdateQueue.poll();
}
if (next == null)
{
return;
}
update(next);
}
@Scheduled(fixedDelay = 6 * 60 * 60 * 1000) // 6 hours
public void clearFilter()
{
usernameFilter = createFilter();
}
private BloomFilter<String> createFilter()
{
final BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
BLOOMFILTER_EXPECTED_INSERTIONS
);
synchronized (usernameUpdateQueue)
{
for (String toUpdate : usernameUpdateQueue)
{
filter.put(toUpdate);
}
}
return filter;
}
/**
* scale how often to check hiscore updates for players based on their rank
* @param playerEntity
* @return
*/
private static Duration updateFrequency(PlayerEntity playerEntity)
{
Integer rank = playerEntity.getRank();
if (rank == null || rank == -1)
{
return Duration.ofDays(7);
}
else if (rank < 10_000)
{
return Duration.ofHours(6);
}
else if (rank < 50_000)
{
return Duration.ofDays(2);
}
else if (rank < 100_000)
{
return Duration.ofDays(5);
}
else
{
return Duration.ofDays(7);
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.xp.beans;
import java.time.Instant;
import lombok.Data;
@Data
public class PlayerEntity
{
private Integer id;
private String name;
private Instant tracked_since;
private Instant last_updated;
private Integer rank;
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.xp.beans;
import java.time.Instant;
import lombok.Data;
@Data
public class XpEntity
{
private Integer id;
private Instant time;
private Integer player;
private int overall_xp;
private int attack_xp;
private int defence_xp;
private int strength_xp;
private int hitpoints_xp;
private int ranged_xp;
private int prayer_xp;
private int magic_xp;
private int cooking_xp;
private int woodcutting_xp;
private int fletching_xp;
private int fishing_xp;
private int firemaking_xp;
private int crafting_xp;
private int smithing_xp;
private int mining_xp;
private int herblore_xp;
private int agility_xp;
private int thieving_xp;
private int slayer_xp;
private int farming_xp;
private int runecraft_xp;
private int hunter_xp;
private int construction_xp;
private int overall_rank;
private int attack_rank;
private int defence_rank;
private int strength_rank;
private int hitpoints_rank;
private int ranged_rank;
private int prayer_rank;
private int magic_rank;
private int cooking_rank;
private int woodcutting_rank;
private int fletching_rank;
private int fishing_rank;
private int firemaking_rank;
private int crafting_rank;
private int smithing_rank;
private int mining_rank;
private int herblore_rank;
private int agility_rank;
private int thieving_rank;
private int slayer_rank;
private int farming_rank;
private int runecraft_rank;
private int hunter_rank;
private int construction_rank;
}

View File

@@ -42,7 +42,7 @@ import org.springframework.web.bind.annotation.RestController;
public class XteaController
{
@Autowired
private XteaService xteaService;
private XteaEndpoint xteaService;
@RequestMapping(method = POST)
public void submit(@RequestBody XteaRequest xteaRequest)

View File

@@ -26,17 +26,9 @@ package net.runelite.http.service.xtea;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.io.IOException;
import java.util.List;
import net.runelite.cache.IndexType;
import net.runelite.cache.fs.Container;
import net.runelite.cache.util.Djb2;
import net.runelite.http.api.xtea.XteaKey;
import net.runelite.http.api.xtea.XteaRequest;
import net.runelite.http.service.cache.CacheService;
import net.runelite.http.service.cache.beans.ArchiveEntry;
import net.runelite.http.service.cache.beans.CacheEntry;
import net.runelite.http.service.util.exception.InternalServerErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@@ -45,52 +37,49 @@ import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
public class XteaService
public class XteaEndpoint
{
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `region` int(11) NOT NULL,\n"
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " `rev` int(11) NOT NULL,\n"
+ " `key1` int(11) NOT NULL,\n"
+ " `key2` int(11) NOT NULL,\n"
+ " `key3` int(11) NOT NULL,\n"
+ " `key4` int(11) NOT NULL,\n"
+ " PRIMARY KEY (`id`),\n"
+ " KEY `region` (`region`,`time`)\n"
+ ") ENGINE=InnoDB";
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `region` int(11) NOT NULL,\n"
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " `rev` int(11) NOT NULL,\n"
+ " `key1` int(11) NOT NULL,\n"
+ " `key2` int(11) NOT NULL,\n"
+ " `key3` int(11) NOT NULL,\n"
+ " `key4` int(11) NOT NULL,\n"
+ " PRIMARY KEY (`id`),\n"
+ " KEY `region` (`region`,`time`)\n"
+ ") ENGINE=InnoDB";
private final Sql2o sql2o;
private final CacheService cacheService;
private final Cache<Integer, XteaCache> keyCache = CacheBuilder.newBuilder()
.maximumSize(1024)
.build();
.maximumSize(1024)
.build();
@Autowired
public XteaService(
@Qualifier("Runelite SQL2O") Sql2o sql2o,
CacheService cacheService
public XteaEndpoint(
@Qualifier("Runelite SQL2O") Sql2o sql2o
)
{
this.sql2o = sql2o;
this.cacheService = cacheService;
try (Connection con = sql2o.beginTransaction())
{
con.createQuery(CREATE_SQL)
.executeUpdate();
.executeUpdate();
}
}
private XteaEntry findLatestXtea(Connection con, int region)
{
return con.createQuery("select region, time, key1, key2, key3, key4 from xtea "
+ "where region = :region "
+ "order by time desc "
+ "limit 1")
.addParameter("region", region)
.executeAndFetchFirst(XteaEntry.class);
+ "where region = :region "
+ "order by time desc "
+ "limit 1")
.addParameter("region", region)
.executeAndFetchFirst(XteaEntry.class);
}
public void submit(XteaRequest xteaRequest)
@@ -103,10 +92,10 @@ public class XteaService
XteaCache xteaCache = keyCache.getIfPresent(region);
if (xteaCache == null
|| xteaCache.getKey1() != keys[0]
|| xteaCache.getKey2() != keys[1]
|| xteaCache.getKey3() != keys[2]
|| xteaCache.getKey4() != keys[3])
|| xteaCache.getKey1() != keys[0]
|| xteaCache.getKey2() != keys[1]
|| xteaCache.getKey3() != keys[2]
|| xteaCache.getKey4() != keys[3])
{
cached = false;
keyCache.put(region, new XteaCache(region, keys[0], keys[1], keys[2], keys[3]));
@@ -120,13 +109,6 @@ public class XteaService
try (Connection con = sql2o.beginTransaction())
{
CacheEntry cache = cacheService.findMostRecent();
if (cache == null)
{
throw new InternalServerErrorException("No most recent cache");
}
Query query = null;
for (XteaKey key : xteaRequest.getKeys())
@@ -142,33 +124,30 @@ public class XteaService
}
// already have these?
// TODO : check if useful / works should check with findLatestXtea
if (xteaEntry != null
&& xteaEntry.getKey1() == keys[0]
&& xteaEntry.getKey2() == keys[1]
&& xteaEntry.getKey3() == keys[2]
&& xteaEntry.getKey4() == keys[3])
&& xteaEntry.getKey1() == keys[0]
&& xteaEntry.getKey2() == keys[1]
&& xteaEntry.getKey3() == keys[2]
&& xteaEntry.getKey4() == keys[3])
{
continue;
}
if (!checkKeys(cache, region, keys))
{
continue;
}
if (query == null)
{
query = con.createQuery("insert into xtea (region, rev, key1, key2, key3, key4) "
+ "values (:region, :rev, :key1, :key2, :key3, :key4)");
+ "values (:region, :rev, :key1, :key2, :key3, :key4)");
}
query.addParameter("region", region)
.addParameter("rev", xteaRequest.getRevision())
.addParameter("key1", keys[0])
.addParameter("key2", keys[1])
.addParameter("key3", keys[2])
.addParameter("key4", keys[3])
.addToBatch();
.addParameter("rev", xteaRequest.getRevision())
.addParameter("key1", keys[0])
.addParameter("key2", keys[1])
.addParameter("key3", keys[2])
.addParameter("key4", keys[3])
.addToBatch();
}
if (query != null)
@@ -184,10 +163,10 @@ public class XteaService
try (Connection con = sql2o.open())
{
return con.createQuery(
"select t1.region, t2.time, t2.rev, t2.key1, t2.key2, t2.key3, t2.key4 from " +
"(select region,max(id) as id from xtea group by region) t1 " +
"join xtea t2 on t1.id = t2.id")
.executeAndFetch(XteaEntry.class);
"select t1.region, t2.time, t2.rev, t2.key1, t2.key2, t2.key3, t2.key4 from " +
"(select region,max(id) as id from xtea group by region) t1 " +
"join xtea t2 on t1.id = t2.id")
.executeAndFetch(XteaEntry.class);
}
}
@@ -196,45 +175,9 @@ public class XteaService
try (Connection con = sql2o.open())
{
return con.createQuery("select region, time, rev, key1, key2, key3, key4 from xtea "
+ "where region = :region order by time desc limit 1")
.addParameter("region", region)
.executeAndFetchFirst(XteaEntry.class);
}
}
private boolean checkKeys(CacheEntry cache, int regionId, int[] keys)
{
int x = regionId >>> 8;
int y = regionId & 0xFF;
String archiveName = new StringBuilder()
.append('l')
.append(x)
.append('_')
.append(y)
.toString();
int archiveNameHash = Djb2.hash(archiveName);
ArchiveEntry archiveEntry = cacheService.findArchiveForTypeAndName(cache, IndexType.MAPS, archiveNameHash);
if (archiveEntry == null)
{
throw new InternalServerErrorException("Unable to find archive for region");
}
byte[] data = cacheService.getArchive(archiveEntry);
if (data == null)
{
throw new InternalServerErrorException("Unable to get archive data");
}
try
{
Container.decompress(data, keys);
return true;
}
catch (IOException ex)
{
return false;
+ "where region = :region order by time desc limit 1")
.addParameter("region", region)
.executeAndFetchFirst(XteaEntry.class);
}
}
}

View File

@@ -30,8 +30,6 @@ redis:
pool.size: 10
host: http://localhost:6379
mongo:
host: mongodb://localhost:27017
# Twitter client for feed
runelite:

View File

@@ -1,110 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.hiscore;
import java.io.IOException;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import net.runelite.http.api.hiscore.HiscoreResult;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class HiscoreServiceTest
{
private static final String RESPONSE = "654683,705,1304518\n"
+ "679419,50,107181\n"
+ "550667,48,85764\n"
+ "861497,50,101366\n"
+ "891591,48,87843\n"
+ "-1,1,4\n"
+ "840255,27,10073\n"
+ "1371912,10,1310\n"
+ "432193,56,199795\n"
+ "495638,56,198304\n"
+ "514466,37,27502\n"
+ "456981,54,159727\n"
+ "459159,49,93010\n"
+ "1028855,8,823\n"
+ "862906,29,12749\n"
+ "795020,31,16097\n"
+ "673591,5,495\n"
+ "352676,51,112259\n"
+ "428419,40,37235\n"
+ "461887,43,51971\n"
+ "598582,1,10\n"
+ "638177,1,0\n"
+ "516239,9,1000\n"
+ "492790,1,0\n"
+ "-1,-1\n"
+ "73,1738\n"
+ "-1,-1\n"
+ "531,1432\n"
+ "324,212\n"
+ "8008,131\n"
+ "1337,911\n"
+ "42,14113\n"
+ "1,777\n"
+ "254,92\n";
private final MockWebServer server = new MockWebServer();
@Before
public void before() throws IOException
{
server.enqueue(new MockResponse().setBody(RESPONSE));
server.start();
}
@After
public void after() throws IOException
{
server.shutdown();
}
@Test
public void testNormalLookup() throws Exception
{
HiscoreTestService hiscores = new HiscoreTestService(server.url("/"));
HiscoreResult result = hiscores.lookupUsername("zezima", HiscoreEndpoint.NORMAL.getHiscoreURL());
Assert.assertEquals(50, result.getAttack().getLevel());
Assert.assertEquals(159727L, result.getFishing().getExperience());
Assert.assertEquals(492790, result.getConstruction().getRank());
Assert.assertEquals(1432, result.getClueScrollAll().getLevel());
Assert.assertEquals(324, result.getClueScrollBeginner().getRank());
Assert.assertEquals(8008, result.getClueScrollEasy().getRank());
Assert.assertEquals(911, result.getClueScrollMedium().getLevel());
Assert.assertEquals(42, result.getClueScrollHard().getRank());
Assert.assertEquals(777, result.getClueScrollElite().getLevel());
Assert.assertEquals(254, result.getClueScrollMaster().getRank());
Assert.assertEquals(-1, result.getLastManStanding().getLevel());
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.hiscore;
import java.io.IOException;
import net.runelite.http.api.hiscore.HiscoreResult;
import okhttp3.HttpUrl;
class HiscoreTestService extends HiscoreService
{
private final HttpUrl testUrl;
HiscoreTestService(HttpUrl testUrl)
{
this.testUrl = testUrl;
}
@Override
public HiscoreResult lookupUsername(String username, HttpUrl endpoint) throws IOException
{
return super.lookupUsername(username, testUrl);
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.loottracker;
import java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.loottracker.GameItem;
import net.runelite.http.api.loottracker.LootRecord;
import net.runelite.http.api.loottracker.LootRecordType;
import net.runelite.http.service.account.AuthFilter;
import net.runelite.http.service.account.beans.SessionEntry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@WebMvcTest(LootTrackerController.class)
@Slf4j
@ActiveProfiles("test")
public class LootTrackerControllerTest
{
@Autowired
private MockMvc mockMvc;
@MockBean
private LootTrackerService lootTrackerService;
@MockBean
private AuthFilter authFilter;
@Before
public void before() throws IOException
{
when(authFilter.handle(any(HttpServletRequest.class), any(HttpServletResponse.class)))
.thenReturn(mock(SessionEntry.class));
}
@Test
public void storeLootRecord() throws Exception
{
LootRecord lootRecord = new LootRecord();
lootRecord.setType(LootRecordType.NPC);
lootRecord.setTime(Instant.now());
lootRecord.setDrops(Collections.singletonList(new GameItem(4151, 1)));
String data = RuneLiteAPI.GSON.toJson(lootRecord);
mockMvc.perform(post("/loottracker").content(data).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
verify(lootTrackerService).store(eq(lootRecord), anyInt());
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.worlds;
import java.io.IOException;
import java.io.InputStream;
import net.runelite.http.api.worlds.World;
import net.runelite.http.api.worlds.WorldResult;
import net.runelite.http.api.worlds.WorldType;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okio.Buffer;
import org.junit.After;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.sql2o.tools.IOUtils;
public class WorldsServiceTest
{
private final MockWebServer server = new MockWebServer();
@Before
public void before() throws IOException
{
InputStream in = WorldsServiceTest.class.getResourceAsStream("worldlist");
byte[] worldData = IOUtils.toByteArray(in);
Buffer buffer = new Buffer();
buffer.write(worldData);
server.enqueue(new MockResponse().setBody(buffer));
server.start();
}
@After
public void after() throws IOException
{
server.shutdown();
}
@Test
public void testListWorlds() throws Exception
{
WorldsService worlds = new WorldsService();
worlds.setUrl(server.url("/"));
WorldResult worldResult = worlds.getWorlds();
assertEquals(82, worldResult.getWorlds().size());
World world = worldResult.findWorld(385);
assertNotNull(world);
assertTrue(world.getTypes().contains(WorldType.SKILL_TOTAL));
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.xp;
import net.runelite.http.api.hiscore.HiscoreResult;
import net.runelite.http.api.hiscore.Skill;
import net.runelite.http.api.xp.XpData;
import net.runelite.http.service.xp.beans.XpEntity;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class XpMapperTest
{
@Test
public void testXpEntityToXpData()
{
XpEntity xpEntity = new XpEntity();
xpEntity.setAgility_rank(42);
xpEntity.setAgility_xp(9001);
XpData xpData = XpMapper.INSTANCE.xpEntityToXpData(xpEntity);
assertEquals(42, xpData.getAgility_rank());
assertEquals(9001, xpData.getAgility_xp());
}
@Test
public void testHiscoreResultToXpData()
{
HiscoreResult hiscoreResult = new HiscoreResult();
hiscoreResult.setAgility(new Skill(42, 9, 9001));
XpData xpData = XpMapper.INSTANCE.hiscoreResultToXpData(hiscoreResult);
assertEquals(42, xpData.getAgility_rank());
assertEquals(9001, xpData.getAgility_xp());
}
}

View File

@@ -99,6 +99,11 @@ public class InjectUtil
if (hint != null)
{
ClassFile c = inject.getDeobfuscated().findClass(hint);
if (c == null)
{
throw new InjectionException("Class " + hint + " doesn't exist. (check capitalization)");
}
for (Field f : c.getFields())
{
if (!f.getName().equals(name))

View File

@@ -157,7 +157,7 @@ public final class AnimationID
public static final int HOME_MAKE_TABLET = 4067;
public static final int THIEVING_STALL = 832;
public static final int PICKPOCKET_SUCCESS = 881;
//block animations for players and perhaps npcs as well?
public static final int BLOCK_DEFENDER = 4177;
public static final int BLOCK_NO_SHIELD = 420;
@@ -252,7 +252,7 @@ public final class AnimationID
public static final int HYDRA_RANGED_4 = 8255;
public static final int HYDRA_4_1 = 8257;
public static final int HYDRA_4_2 = 8258;
// INFERNO animations
public static final int JAL_NIB = 7574;
public static final int JAL_MEJRAH = 7578;
@@ -278,4 +278,31 @@ public final class AnimationID
public static final int GENERAL_AUTO1 = 7018;
public static final int GENERAL_AUTO2 = 7020;
public static final int GENERAL_AUTO3 = 7021;
}
//Zammy-poo
public static final int ZAMMY_GENERIC_AUTO = 64;
public static final int KRIL_AUTO = 6948;
public static final int KRIL_SPEC = 6950;
public static final int ZAKL_AUTO = 7077;
public static final int BALFRUG_AUTO = 4630;
//Sara-Poo
public static final int ZILYANA_MELEE_AUTO = 6964;
public static final int ZILYANA_AUTO = 6967;
public static final int ZILYANA_SPEC = 6970;
public static final int STARLIGHT_AUTO = 6376;
public static final int BREE_AUTO = 7026;
public static final int GROWLER_AUTO = 7037;
//Arma-Poo
public static final int KREE_RANGED = 6978;
public static final int SKREE_AUTO = 6955;
public static final int GEERIN_AUTO = 6956;
public static final int GEERIN_FLINCH = 6958;
public static final int KILISA_AUTO = 6957;
//Dag Kings
public static final int DAG_REX = 2853;
public static final int DAG_PRIME = 2854;
public static final int DAG_SUPREME = 2855;
}

View File

@@ -249,13 +249,11 @@ public interface Client extends GameShell
/**
* Gets the canvas height
* @return
*/
int getCanvasHeight();
/**
* Gets the canvas width
* @return
*/
int getCanvasWidth();
@@ -413,23 +411,6 @@ public interface Client extends GameShell
*/
int getMouseCurrentButton();
/**
* Schedules checking of current region tile for next frame, so ${@link Client#getSelectedSceneTile()} ()} will
* return actual value.
*
* @param checkClick when true next frame selected region tile will be updated
*/
void setCheckClick(boolean checkClick);
/**
* Sets current mouse hover position. This value is automatically updated only when right-clicking in game.
* Setting this value together with ${@link Client#setCheckClick(boolean)} will update ${@link Client#getSelectedSceneTile()} ()}
* for next frame.
*
* @param position current mouse hover position
*/
void setMouseCanvasHoverPosition(Point position);
/**
* Gets the currently selected tile (ie. last right clicked tile).
*
@@ -724,7 +705,7 @@ public interface Client extends GameShell
* @param varps passed varbits
* @param varbitId the variable ID
* @return the value
* @see Varbits#id
* @see Varbits
*/
int getVarbitValue(int[] varps, int varbitId);
@@ -754,7 +735,7 @@ public interface Client extends GameShell
* @param varps passed varbits
* @param varbit the variable
* @param value the value
* @see Varbits#id
* @see Varbits
*/
void setVarbitValue(int[] varps, int varbit, int value);
@@ -798,8 +779,6 @@ public interface Client extends GameShell
/**
* Get the total experience of the player
*
* @return
*/
long getOverallExperience();
@@ -977,6 +956,28 @@ public interface Client extends GameShell
*/
void playSoundEffect(int id, int x, int y, int range);
/**
* Play a sound effect from some point in the world.
*
* @param id the ID of the sound to play. Any int is allowed, but see
* {@link SoundEffectID} for some common ones
* @param x the ground coordinate on the x axis
* @param y the ground coordinate on the y axis
* @param range the number of tiles away that the sound can be heard
* from
* @param delay the amount of frames before the sound starts playing
*/
void playSoundEffect(int id, int x, int y, int range, int delay);
/**
* Plays a sound effect, even if the player's sound effect volume is muted.
*
* @param id the ID of the sound effect - {@link SoundEffectID}
* @param volume the volume to play the sound effect at, see {@link SoundEffectVolume} for values used
* in the settings interface. if the sound effect volume is not muted, uses the set volume
*/
void playSoundEffect(int id, int volume);
/**
* Gets the clients graphic buffer provider.
*
@@ -1078,15 +1079,11 @@ public interface Client extends GameShell
/**
* Gets the clan owner of the currently joined clan chat
*
* @return
*/
String getClanOwner();
/**
* Gets the clan chat name of the currently joined clan chat
*
* @return
*/
String getClanChatName();
@@ -1099,22 +1096,16 @@ public interface Client extends GameShell
/**
* Gets the number of friends on the friends list.
*
* @return
*/
int getFriendsCount();
/**
* Gets an array of players on the ignore list.
*
* @return
*/
Ignore[] getIgnores();
/**
* Gets the number of ignored players on the ignore list.
*
* @return
*/
int getIgnoreCount();
@@ -1618,14 +1609,11 @@ public interface Client extends GameShell
/**
* Get the if1 widget whose item is being dragged
*
* @return
*/
Widget getIf1DraggedWidget();
/**
* Get the item index of the item being dragged on an if1 widget
* @return
*/
int getIf1DraggedItemIndex();

View File

@@ -42,4 +42,14 @@ public interface DecorativeObject extends TileObject
Renderable getRenderable();
Renderable getRenderable2();
Model getModel1();
Model getModel2();
int getYOffset();
int getXOffset();
int getOrientation();
}

View File

@@ -69,4 +69,8 @@ public interface GameObject extends TileObject
Angle getOrientation();
Renderable getRenderable();
int getRsOrientation();
Model getModel();
}

View File

@@ -30,4 +30,6 @@ package net.runelite.api;
public interface GroundObject extends TileObject
{
Renderable getRenderable();
Model getModel();
}

View File

@@ -56,4 +56,8 @@ public interface ItemLayer extends TileObject
* @return the top item
*/
Renderable getTop();
Model getModelBottom();
Model getModelMiddle();
Model getModelTop();
}

View File

@@ -70,4 +70,17 @@ public class MenuEntry
* This is used for shift click
*/
private boolean forceLeftClick;
public static MenuEntry copy(MenuEntry src)
{
return new MenuEntry(
src.getOption(),
src.getTarget(),
src.getIdentifier(),
src.getType(),
src.getParam0(),
src.getParam1(),
src.isForceLeftClick()
);
}
}

View File

@@ -24,155 +24,147 @@
*/
package net.runelite.api;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.widgets.WidgetInfo;
/**
* An enumeration of different prayer spells.
*/
@Getter
@AllArgsConstructor
public enum Prayer
{
/**
* Thick Skin (Level 1, Defence).
*/
THICK_SKIN(Varbits.PRAYER_THICK_SKIN, 5.0),
THICK_SKIN(Varbits.PRAYER_THICK_SKIN, 5.0, WidgetInfo.PRAYER_THICK_SKIN),
/**
* Burst of Strength (Level 4, Strength).
*/
BURST_OF_STRENGTH(Varbits.PRAYER_BURST_OF_STRENGTH, 5.0),
BURST_OF_STRENGTH(Varbits.PRAYER_BURST_OF_STRENGTH, 5.0, WidgetInfo.PRAYER_BURST_OF_STRENGTH),
/**
* Clarity of Thought (Level 7, Attack).
*/
CLARITY_OF_THOUGHT(Varbits.PRAYER_CLARITY_OF_THOUGHT, 5.0),
CLARITY_OF_THOUGHT(Varbits.PRAYER_CLARITY_OF_THOUGHT, 5.0, WidgetInfo.PRAYER_CLARITY_OF_THOUGHT),
/**
* Sharp Eye (Level 8, Ranging).
*/
SHARP_EYE(Varbits.PRAYER_SHARP_EYE, 5.0),
SHARP_EYE(Varbits.PRAYER_SHARP_EYE, 5.0, WidgetInfo.PRAYER_SHARP_EYE),
/**
* Mystic Will (Level 9, Magic).
*/
MYSTIC_WILL(Varbits.PRAYER_MYSTIC_WILL, 5.0),
MYSTIC_WILL(Varbits.PRAYER_MYSTIC_WILL, 5.0, WidgetInfo.PRAYER_MYSTIC_WILL),
/**
* Rock Skin (Level 10, Defence).
*/
ROCK_SKIN(Varbits.PRAYER_ROCK_SKIN, 10.0),
ROCK_SKIN(Varbits.PRAYER_ROCK_SKIN, 10.0, WidgetInfo.PRAYER_ROCK_SKIN),
/**
* Superhuman Strength (Level 13, Strength).
*/
SUPERHUMAN_STRENGTH(Varbits.PRAYER_SUPERHUMAN_STRENGTH, 10.0),
SUPERHUMAN_STRENGTH(Varbits.PRAYER_SUPERHUMAN_STRENGTH, 10.0, WidgetInfo.PRAYER_SUPERHUMAN_STRENGTH),
/**
* Improved Reflexes (Level 16, Attack).
*/
IMPROVED_REFLEXES(Varbits.PRAYER_IMPROVED_REFLEXES, 10.0),
IMPROVED_REFLEXES(Varbits.PRAYER_IMPROVED_REFLEXES, 10.0, WidgetInfo.PRAYER_IMPROVED_REFLEXES),
/**
* Rapid Restore (Level 19, Stats).
*/
RAPID_RESTORE(Varbits.PRAYER_RAPID_RESTORE, 60.0 / 36.0),
RAPID_RESTORE(Varbits.PRAYER_RAPID_RESTORE, 60.0 / 36.0, WidgetInfo.PRAYER_RAPID_RESTORE),
/**
* Rapid Heal (Level 22, Hitpoints).
*/
RAPID_HEAL(Varbits.PRAYER_RAPID_HEAL, 60.0 / 18),
RAPID_HEAL(Varbits.PRAYER_RAPID_HEAL, 60.0 / 18, WidgetInfo.PRAYER_RAPID_HEAL),
/**
* Protect Item (Level 25).
*/
PROTECT_ITEM(Varbits.PRAYER_PROTECT_ITEM, 60.0 / 18),
PROTECT_ITEM(Varbits.PRAYER_PROTECT_ITEM, 60.0 / 18, WidgetInfo.PRAYER_PROTECT_ITEM),
/**
* Hawk Eye (Level 26, Ranging).
*/
HAWK_EYE(Varbits.PRAYER_HAWK_EYE, 10.0),
HAWK_EYE(Varbits.PRAYER_HAWK_EYE, 10.0, WidgetInfo.PRAYER_HAWK_EYE),
/**
* Mystic Lore (Level 27, Magic).
*/
MYSTIC_LORE(Varbits.PRAYER_MYSTIC_LORE, 10.0),
MYSTIC_LORE(Varbits.PRAYER_MYSTIC_LORE, 10.0, WidgetInfo.PRAYER_MYSTIC_LORE),
/**
* Steel Skin (Level 28, Defence).
*/
STEEL_SKIN(Varbits.PRAYER_STEEL_SKIN, 20.0),
STEEL_SKIN(Varbits.PRAYER_STEEL_SKIN, 20.0, WidgetInfo.PRAYER_STEEL_SKIN),
/**
* Ultimate Strength (Level 31, Strength).
*/
ULTIMATE_STRENGTH(Varbits.PRAYER_ULTIMATE_STRENGTH, 20.0),
ULTIMATE_STRENGTH(Varbits.PRAYER_ULTIMATE_STRENGTH, 20.0, WidgetInfo.PRAYER_ULTIMATE_STRENGTH),
/**
* Incredible Reflexes (Level 34, Attack).
*/
INCREDIBLE_REFLEXES(Varbits.PRAYER_INCREDIBLE_REFLEXES, 20.0),
INCREDIBLE_REFLEXES(Varbits.PRAYER_INCREDIBLE_REFLEXES, 20.0, WidgetInfo.PRAYER_INCREDIBLE_REFLEXES),
/**
* Protect from Magic (Level 37).
*/
PROTECT_FROM_MAGIC(Varbits.PRAYER_PROTECT_FROM_MAGIC, 20.0),
PROTECT_FROM_MAGIC(Varbits.PRAYER_PROTECT_FROM_MAGIC, 20.0, WidgetInfo.PRAYER_PROTECT_FROM_MAGIC),
/**
* Protect from Missiles (Level 40).
*/
PROTECT_FROM_MISSILES(Varbits.PRAYER_PROTECT_FROM_MISSILES, 20.0),
PROTECT_FROM_MISSILES(Varbits.PRAYER_PROTECT_FROM_MISSILES, 20.0, WidgetInfo.PRAYER_PROTECT_FROM_MISSILES),
/**
* Protect from Melee (Level 43).
*/
PROTECT_FROM_MELEE(Varbits.PRAYER_PROTECT_FROM_MELEE, 20.0),
PROTECT_FROM_MELEE(Varbits.PRAYER_PROTECT_FROM_MELEE, 20.0, WidgetInfo.PRAYER_PROTECT_FROM_MELEE),
/**
* Eagle Eye (Level 44, Ranging).
*/
EAGLE_EYE(Varbits.PRAYER_EAGLE_EYE, 20.0),
EAGLE_EYE(Varbits.PRAYER_EAGLE_EYE, 20.0, WidgetInfo.PRAYER_EAGLE_EYE),
/**
* Mystic Might (Level 45, Magic).
*/
MYSTIC_MIGHT(Varbits.PRAYER_MYSTIC_MIGHT, 20.0),
MYSTIC_MIGHT(Varbits.PRAYER_MYSTIC_MIGHT, 20.0, WidgetInfo.PRAYER_MYSTIC_MIGHT),
/**
* Retribution (Level 46).
*/
RETRIBUTION(Varbits.PRAYER_RETRIBUTION, 5.0),
RETRIBUTION(Varbits.PRAYER_RETRIBUTION, 5.0, WidgetInfo.PRAYER_RETRIBUTION),
/**
* Redemption (Level 49).
*/
REDEMPTION(Varbits.PRAYER_REDEMPTION, 10.0),
REDEMPTION(Varbits.PRAYER_REDEMPTION, 10.0, WidgetInfo.PRAYER_REDEMPTION),
/**
* Smite (Level 52).
*/
SMITE(Varbits.PRAYER_SMITE, 30.0),
SMITE(Varbits.PRAYER_SMITE, 30.0, WidgetInfo.PRAYER_SMITE),
/**
* Chivalry (Level 60, Defence/Strength/Attack).
*/
CHIVALRY(Varbits.PRAYER_CHIVALRY, 40.0),
CHIVALRY(Varbits.PRAYER_CHIVALRY, 40.0, WidgetInfo.PRAYER_CHIVALRY),
/**
* Piety (Level 70, Defence/Strength/Attack).
*/
PIETY(Varbits.PRAYER_PIETY, 40.0),
PIETY(Varbits.PRAYER_PIETY, 40.0, WidgetInfo.PRAYER_PIETY),
/**
* Preserve (Level 55).
*/
PRESERVE(Varbits.PRAYER_PRESERVE, 60.0 / 18),
PRESERVE(Varbits.PRAYER_PRESERVE, 60.0 / 18, WidgetInfo.PRAYER_PRESERVE),
/**
* Rigour (Level 74, Ranging/Damage/Defence).
*/
RIGOUR(Varbits.PRAYER_RIGOUR, 40.0),
RIGOUR(Varbits.PRAYER_RIGOUR, 40.0, WidgetInfo.PRAYER_RIGOUR),
/**
* Augury (Level 77, Magic/Magic Def./Defence).
*/
AUGURY(Varbits.PRAYER_AUGURY, 40.0);
private final Varbits varbit;
private final double drainRate;
Prayer(Varbits varbit, double drainRate)
{
this.varbit = varbit;
this.drainRate = drainRate;
}
AUGURY(Varbits.PRAYER_AUGURY, 40.0, WidgetInfo.PRAYER_AUGURY);
/**
* Gets the varbit that stores whether the prayer is active or not.
*
* @return the prayer active varbit
*/
public Varbits getVarbit()
{
return varbit;
}
private final Varbits varbit;
/**
* Gets the prayer drain rate (measured in pray points/minute)
*
* @return the prayer drain rate
*/
public double getDrainRate()
{
return drainRate;
}
}
private final double drainRate;
/**
* Gets the widget info for prayer
*/
private final WidgetInfo widgetInfo;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* Copyright (c) 2018, trimbe <github.com/trimbe>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -22,17 +22,16 @@
* (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;
package net.runelite.api;
import lombok.Data;
@Data
public class ArchiveEntry
/**
* Volume values for each of the stops on the volume interface
*/
public final class SoundEffectVolume
{
private int id;
private int archiveId;
private int nameHash;
private int crc;
private int revision;
private byte[] hash;
public static final int MUTED = 0;
public static final int LOW = 32;
public static final int MEDIUM_LOW = 64;
public static final int MEDIUM_HIGH = 96;
public static final int HIGH = 127;
}

View File

@@ -52,4 +52,7 @@ public interface WallObject extends TileObject
Renderable getRenderable1();
Renderable getRenderable2();
Model getModelA();
Model getModelB();
}

View File

@@ -72,6 +72,30 @@ public enum WorldType
private static final EnumSet<WorldType> HIGHRISK_WORLD_TYPES = EnumSet.of(
HIGH_RISK
);
private static final EnumSet<WorldType> ALL_HIGHRISK_WORLD_TYPES = EnumSet.of(
HIGH_RISK,
DEADMAN,
DEADMAN_TOURNAMENT,
SEASONAL_DEADMAN
);
private static final EnumSet<WorldType> ALL_PVP_WORLD_TYPES = EnumSet.of(
HIGH_RISK,
DEADMAN,
DEADMAN_TOURNAMENT,
PVP,
SEASONAL_DEADMAN
);
private static final EnumSet<WorldType> ALL_PK_WORLD_TYPES = EnumSet.of(
HIGH_RISK,
DEADMAN,
DEADMAN_TOURNAMENT,
PVP,
SEASONAL_DEADMAN,
BOUNTY
);
/**
* Create enum set of world types from mask.
@@ -133,4 +157,19 @@ public enum WorldType
{
return worldTypes.stream().anyMatch(HIGHRISK_WORLD_TYPES::contains);
}
public static boolean isAllHighRiskWorld(final Collection<WorldType> worldTypes)
{
return worldTypes.stream().anyMatch(ALL_HIGHRISK_WORLD_TYPES::contains);
}
public static boolean isAllPvpWorld(final Collection<WorldType> worldTypes)
{
return worldTypes.stream().anyMatch(ALL_PVP_WORLD_TYPES::contains);
}
public static boolean isAllPKWorld(final Collection<WorldType> worldTypes)
{
return worldTypes.stream().anyMatch(ALL_PK_WORLD_TYPES::contains);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* Copyright (c) 2018, WooxSolo <https://github.com/WooxSolo>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -22,14 +22,16 @@
* (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;
package net.runelite.api.events;
import lombok.Data;
@Data
class TwitterStatusesResponseItem
public class AreaSoundEffectPlayed
{
private long id;
private String text;
private TwitterStatusesResponseItemUser user;
private int soundId;
private int sceneX;
private int sceneY;
private int range;
private int delay;
}

View File

@@ -42,10 +42,15 @@ import net.runelite.api.MenuEntry;
@Data
public class MenuOptionClicked
{
public MenuOptionClicked(MenuEntry entry)
{
menuEntry = entry;
}
/**
* The actual MenuEntry object representing what was clicked
*/
private final MenuEntry menuEntry;
private MenuEntry menuEntry;
/**
* The option text added to the menu.

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* Copyright (c) 2019, 7ate9 <https://github.com/se7enAte9>
* Copyright (c) 2019, https://runelitepl.us
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -22,16 +23,19 @@
* (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;
package net.runelite.api.events;
import java.util.Map;
import net.runelite.api.Projectile;
import lombok.Data;
/**
* An event called whenever a {@link Projectile} has spawned.
*/
@Data
public class RSPrices
public class ProjectileSpawned
{
/**
* unix time in ms to price in gp
* The spawned projectile.
*/
private Map<Long, Integer> daily;
private Projectile projectile;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* Copyright (c) 2018, WooxSolo <https://github.com/WooxSolo>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -22,13 +22,13 @@
* (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;
package net.runelite.api.events;
import java.util.List;
import lombok.Data;
@Data
public class RSSearch
public class SoundEffectPlayed
{
private List<RSItem> items;
private int soundId;
private int delay;
}

View File

@@ -57,7 +57,7 @@ public class WidgetID
public static final int PEST_CONTROL_BOAT_GROUP_ID = 407;
public static final int PEST_CONTROL_GROUP_ID = 408;
public static final int PEST_CONTROL_EXCHANGE_WINDOW_GROUP_ID = 243;
public static final int PEST_CONTROL_DIALOG_GROUP_ID = 229;
public static final int DIALOG_MINIGAME_GROUP_ID = 229;
public static final int CLAN_CHAT_GROUP_ID = 7;
public static final int MINIMAP_GROUP_ID = 160;
public static final int LOGIN_CLICK_TO_PLAY_GROUP_ID = 378;
@@ -84,6 +84,7 @@ public class WidgetID
public static final int BA_DEFENDER_GROUP_ID = 487;
public static final int BA_HEALER_GROUP_ID = 488;
public static final int BA_REWARD_GROUP_ID = 497;
public static final int BA_HORN_OF_GLORY = 484;
public static final int LEVEL_UP_GROUP_ID = 233;
public static final int DIALOG_SPRITE_GROUP_ID = 193;
public static final int QUEST_COMPLETED_GROUP_ID = 277;
@@ -197,7 +198,7 @@ public class WidgetID
static final int CONFIRM_BUTTON = 6;
}
static class PestControlDialog
static class MinigameDialog
{
static final int TEXT = 1;
static final int CONTINUE = 2;
@@ -594,7 +595,8 @@ public class WidgetID
{
static class ATK
{
static final int LISTEN = 8;
static final int LISTEN_TOP = 7;
static final int LISTEN_BOTTOM = 8;
static final int TO_CALL_WIDGET = 9;
static final int TO_CALL = 10;
static final int ROLE_SPRITE = 11;
@@ -607,6 +609,13 @@ public class WidgetID
static final int TEAMMATE3 = 26;
static final int TEAMMATE4 = 30;
}
static class HORN_GLORY
{
static final int ATTACKER = 5;
static final int DEFENDER = 6;
static final int COLLECTOR = 7;
static final int HEALER = 8;
}
static class REWARD_VALUES
{
static final int RUNNERS_PASSED = 14;

View File

@@ -85,8 +85,8 @@ public enum WidgetInfo
DIARY_QUEST_WIDGET_TITLE(WidgetID.DIARY_QUEST_GROUP_ID, WidgetID.Diary.DIARY_TITLE),
DIARY_QUEST_WIDGET_TEXT(WidgetID.DIARY_QUEST_GROUP_ID, WidgetID.Diary.DIARY_TEXT),
PEST_CONTROL_DIALOG(WidgetID.PEST_CONTROL_DIALOG_GROUP_ID, 0),
PEST_CONTROL_DIALOG_TEXT(WidgetID.PEST_CONTROL_DIALOG_GROUP_ID, WidgetID.PestControlDialog.TEXT),
MINIGAME_DIALOG(WidgetID.DIALOG_MINIGAME_GROUP_ID, 0),
MINIGAME_DIALOG_TEXT(WidgetID.DIALOG_MINIGAME_GROUP_ID, WidgetID.MinigameDialog.TEXT),
PEST_CONTROL_EXCHANGE_WINDOW(WidgetID.PEST_CONTROL_EXCHANGE_WINDOW_GROUP_ID, 0),
PEST_CONTROL_EXCHANGE_WINDOW_POINTS(WidgetID.PEST_CONTROL_EXCHANGE_WINDOW_GROUP_ID, WidgetID.PestControlExchangeWindow.POINTS),
PEST_CONTROL_BOAT_INFO(WidgetID.PEST_CONTROL_BOAT_GROUP_ID, WidgetID.PestControlBoat.INFO),
@@ -310,6 +310,7 @@ public enum WidgetInfo
QUICK_PRAYER_PRAYERS(WidgetID.QUICK_PRAYERS_GROUP_ID, WidgetID.QuickPrayer.PRAYERS),
COMBAT_LEVEL(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.LEVEL),
COMBAT_WEAPON(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.WEAPON_NAME),
COMBAT_STYLE_ONE(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.STYLE_ONE),
COMBAT_STYLE_TWO(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.STYLE_TWO),
COMBAT_STYLE_THREE(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.STYLE_THREE),
@@ -364,6 +365,7 @@ public enum WidgetInfo
BA_HEAL_WAVE_TEXT(WidgetID.BA_HEALER_GROUP_ID, WidgetID.BarbarianAssault.CURRENT_WAVE),
BA_HEAL_CALL_TEXT(WidgetID.BA_HEALER_GROUP_ID, WidgetID.BarbarianAssault.TO_CALL),
BA_HEAL_LISTEN_TEXT(WidgetID.BA_HEALER_GROUP_ID, WidgetID.BarbarianAssault.LISTEN),
BA_HEAL_HORN_LISTEN_TEXT(WidgetID.BA_HORN_OF_GLORY, WidgetID.BarbarianAssault.HORN_GLORY.HEALER),
BA_HEAL_ROLE_TEXT(WidgetID.BA_HEALER_GROUP_ID, WidgetID.BarbarianAssault.ROLE),
BA_HEAL_ROLE_SPRITE(WidgetID.BA_HEALER_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE),
@@ -375,18 +377,22 @@ public enum WidgetInfo
BA_COLL_WAVE_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.CURRENT_WAVE),
BA_COLL_CALL_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.TO_CALL),
BA_COLL_LISTEN_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.LISTEN),
BA_COLL_HORN_LISTEN_TEXT(WidgetID.BA_HORN_OF_GLORY, WidgetID.BarbarianAssault.HORN_GLORY.COLLECTOR),
BA_COLL_ROLE_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.ROLE),
BA_COLL_ROLE_SPRITE(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE),
BA_ATK_WAVE_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.CURRENT_WAVE),
BA_ATK_CALL_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.ATK.TO_CALL),
BA_ATK_LISTEN_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.ATK.LISTEN),
BA_ATK_LISTEN_TOP_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.ATK.LISTEN_TOP),
BA_ATK_LISTEN_BOTTOM_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.ATK.LISTEN_BOTTOM),
BA_ATK_HORN_LISTEN_TEXT(WidgetID.BA_HORN_OF_GLORY, WidgetID.BarbarianAssault.HORN_GLORY.ATTACKER),
BA_ATK_ROLE_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.ATK.ROLE),
BA_ATK_ROLE_SPRITE(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.ATK.ROLE_SPRITE),
BA_DEF_WAVE_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.CURRENT_WAVE),
BA_DEF_CALL_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.TO_CALL),
BA_DEF_LISTEN_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.LISTEN),
BA_DEF_HORN_LISTEN_TEXT(WidgetID.BA_HORN_OF_GLORY, WidgetID.BarbarianAssault.HORN_GLORY.DEFENDER),
BA_DEF_ROLE_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE),
BA_DEF_ROLE_SPRITE(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE),

View File

@@ -45,7 +45,6 @@ import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.MainBufferProvider;
import net.runelite.api.NullItemID;
import net.runelite.api.Point;
import net.runelite.api.RenderOverview;
import net.runelite.api.Renderable;
import net.runelite.api.WorldMapManager;
@@ -375,9 +374,6 @@ public class Hooks implements Callbacks
/**
* Copy an image
*
* @param src
* @return
*/
private static Image copy(Image src)
{
@@ -397,18 +393,6 @@ public class Hooks implements Callbacks
BufferedImage image = (BufferedImage) bufferProvider.getImage();
Graphics2D graphics2d = image.createGraphics();
// Update selected scene tile
if (!client.isMenuOpen())
{
Point p = client.getMouseCanvasPosition();
p = new Point(
p.getX() - client.getViewportXOffset(),
p.getY() - client.getViewportYOffset());
client.setCheckClick(true);
client.setMouseCanvasHoverPosition(p);
}
try
{
renderer.render(graphics2d, OverlayLayer.ABOVE_SCENE);

View File

@@ -51,16 +51,24 @@ public @interface ConfigItem
String unhide() default "";
String unhideValue() default "";
String hide() default "";
String hideValue() default "";
String parent() default "";
String enabledBy() default "";
String enabledByValue() default "";
String disabledBy() default "";
String disabledByValue() default "";
boolean parse() default false;
Class<?> clazz() default void.class;
String method() default "";
}
}

View File

@@ -432,7 +432,10 @@ public class ConfigManager
String current = getConfiguration(group.value(), item.keyName());
String valueString = objectToString(defaultValue);
if (Objects.equals(current, valueString))
// null and the empty string are treated identically in sendConfig and treated as an unset
// If a config value defaults to "" and the current value is null, it will cause an extra
// unset to be sent, so treat them as equal
if (Objects.equals(current, valueString) || (Strings.isNullOrEmpty(current) && Strings.isNullOrEmpty(valueString)))
{
continue; // already set to the default value
}

View File

@@ -325,7 +325,7 @@ public enum AgilityShortcut
TROLLHEIM_MEDIUM_CLIFF_SCRAMBLE_NORTH(43, "Rocks", new WorldPoint(2886, 3684, 0), ROCKS_3803, ROCKS_3804, ROCKS_16522),
TROLLHEIM_MEDIUM_CLIFF_SCRAMBLE_SOUTH(43, "Rocks", new WorldPoint(2876, 3666, 0), ROCKS_3803, ROCKS_3804, ROCKS_16522),
TROLLHEIM_ADVANCED_CLIFF_SCRAMBLE(44, "Rocks", new WorldPoint(2907, 3686, 0), ROCKS_16523, ROCKS_3748),
KOUREND_RIVER_STEPPING_STONES(45, "Stepping Stones", new WorldPoint(1721, 3509, 0), STEPPING_STONE_29728),
KOUREND_RIVER_STEPPING_STONES(45, "Stepping Stones", new WorldPoint(1720, 3551, 0), STEPPING_STONE_29728),
TIRANNWN_LOG_BALANCE(45, "Log Balance", null, LOG_BALANCE_3933, LOG_BALANCE_3931, LOG_BALANCE_3930, LOG_BALANCE_3929, LOG_BALANCE_3932),
COSMIC_ALTAR_MEDIUM_WALKWAY(46, "Narrow Walkway", new WorldPoint(2399, 4403, 0), JUTTING_WALL_17002),
DEEP_WILDERNESS_DUNGEON_CREVICE_NORTH(46, "Narrow Crevice", new WorldPoint(3047, 10335, 0), CREVICE_19043),

View File

@@ -59,6 +59,7 @@ public class NPCManager
/**
* Returns the {@link NPCStats} for target NPC id
*
* @param npcId NPC id
* @return the {@link NPCStats} or null if unknown
*/
@@ -70,23 +71,41 @@ public class NPCManager
/**
* Returns health for target NPC ID
*
* @param npcId NPC id
* @return health or null if unknown
*/
@Nullable
public Integer getHealth(final int npcId)
public int getHealth(final int npcId)
{
final NPCStats s = statsMap.get(npcId);
if (s == null || s.getHitpoints() == -1)
{
return null;
return -1;
}
return s.getHitpoints();
}
/**
* Returns the attack speed for target NPC ID.
*
* @param npcId NPC id
* @return attack speed in game ticks for NPC ID.
*/
public int getAttackSpeed(final int npcId)
{
final NPCStats s = statsMap.get(npcId);
if (s == null || s.getAttackSpeed() == -1)
{
return -1;
}
return s.getAttackSpeed();
}
/**
* Returns the exp modifier for target NPC ID based on its stats.
*
* @param npcId NPC id
* @return npcs exp modifier. Assumes default xp rate if npc stats are unknown (returns 1)
*/

View File

@@ -34,6 +34,7 @@ public class NPCStats
private final int hitpoints;
private final int combatLevel;
private final int slayerLevel;
private final int attackSpeed;
private final int attackLevel;
private final int strengthLevel;

View File

@@ -34,12 +34,18 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import net.runelite.api.Client;
import net.runelite.api.DecorativeObject;
import net.runelite.api.GameObject;
import net.runelite.api.GroundObject;
import net.runelite.api.ItemLayer;
import net.runelite.api.MainBufferProvider;
import net.runelite.api.Model;
import net.runelite.api.NPC;
import net.runelite.api.NPCDefinition;
import net.runelite.api.Perspective;
import net.runelite.api.Player;
import net.runelite.api.TileObject;
import net.runelite.api.WallObject;
import net.runelite.api.coords.LocalPoint;
import net.runelite.client.task.Schedule;
@@ -278,8 +284,7 @@ public class ModelOutlineRenderer
* @param x1 The starting x position
* @param x2 The ending x position
*/
private void simulateHorizontalLineRasterizationForOutline(
int pixelPos, int x1, int x2)
private void simulateHorizontalLineRasterizationForOutline(int pixelPos, int x1, int x2)
{
if (x2 > clipX2)
{
@@ -337,8 +342,7 @@ public class ModelOutlineRenderer
* @param x3 The starting x position of the second line
* @param x4 The ending x position of the second line
*/
private void outlineAroundHorizontalLine(
int pixelPos, int x1, int x2, int x3, int x4)
private void outlineAroundHorizontalLine(int pixelPos, int x1, int x2, int x3, int x4)
{
if (x1 < clipX1)
{
@@ -420,8 +424,7 @@ public class ModelOutlineRenderer
* @param x3 The x position of the third vertex in the triangle
* @param y3 The y position of the third vertex in the triangle
*/
private void simulateTriangleRasterizationForOutline(
int x1, int y1, int x2, int y2, int x3, int y3)
private void simulateTriangleRasterizationForOutline(int x1, int y1, int x2, int y2, int x3, int y3)
{
// Swap vertices so y1 <= y2 <= y3 using bubble sort
if (y1 > y2)
@@ -615,8 +618,7 @@ public class ModelOutlineRenderer
* @param vertexOrientation The orientation of the vertices
* @return Returns true if any of them are inside the clip area, otherwise false
*/
private boolean projectVertices(Model model,
final int localX, final int localY, final int localZ, final int vertexOrientation)
private boolean projectVertices(Model model, final int localX, final int localY, final int localZ, final int vertexOrientation)
{
final int cameraX = client.getCameraX();
final int cameraY = client.getCameraY();
@@ -742,8 +744,7 @@ public class ModelOutlineRenderer
* @param innerColor The color of the pixels of the outline closest to the model
* @param outerColor The color of the pixels of the outline furthest away from the model
*/
private void renderOutline(BufferedImage image, int outlineWidth,
Color innerColor, Color outerColor)
private void renderOutline(BufferedImage image, int outlineWidth, Color innerColor, Color outerColor)
{
int[] imageData = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
List<PixelDistanceAlpha> ps = getPriorityList(outlineWidth);
@@ -886,9 +887,7 @@ public class ModelOutlineRenderer
* @param innerColor The color of the pixels of the outline closest to the model
* @param outerColor The color of the pixels of the outline furthest away from the model
*/
private void drawModelOutline(Model model,
int localX, int localY, int localZ, int orientation,
int outlineWidth, Color innerColor, Color outerColor)
private void drawModelOutline(Model model, int localX, int localY, int localZ, int orientation, int outlineWidth, Color innerColor, Color outerColor)
{
if (outlineWidth <= 0)
{
@@ -930,8 +929,7 @@ public class ModelOutlineRenderer
drawOutline(npc, outlineWidth, color, color);
}
public void drawOutline(NPC npc, int outlineWidth,
Color innerColor, Color outerColor)
public void drawOutline(NPC npc, int outlineWidth, Color innerColor, Color outerColor)
{
int size = 1;
NPCDefinition composition = npc.getTransformedDefinition();
@@ -959,8 +957,7 @@ public class ModelOutlineRenderer
drawOutline(player, outlineWidth, color, color);
}
public void drawOutline(Player player, int outlineWidth,
Color innerColor, Color outerColor)
public void drawOutline(Player player, int outlineWidth, Color innerColor, Color outerColor)
{
LocalPoint lp = player.getLocalLocation();
if (lp != null)
@@ -970,4 +967,136 @@ public class ModelOutlineRenderer
player.getOrientation(), outlineWidth, innerColor, outerColor);
}
}
private void drawOutline(GameObject gameObject, int outlineWidth, Color innerColor, Color outerColor)
{
LocalPoint lp = gameObject.getLocalLocation();
if (lp != null)
{
drawModelOutline(gameObject.getModel(), lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, gameObject.getPlane()),
gameObject.getRsOrientation(), outlineWidth, innerColor, outerColor);
}
}
private void drawOutline(GroundObject groundObject, int outlineWidth, Color innerColor, Color outerColor)
{
LocalPoint lp = groundObject.getLocalLocation();
if (lp != null)
{
drawModelOutline(groundObject.getModel(), lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, client.getPlane()),
0, outlineWidth, innerColor, outerColor);
}
}
private void drawOutline(ItemLayer itemLayer, int outlineWidth, Color innerColor, Color outerColor)
{
LocalPoint lp = itemLayer.getLocalLocation();
if (lp != null)
{
Model model = itemLayer.getModelBottom();
if (model != null)
{
drawModelOutline(model, lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, itemLayer.getPlane()),
0, outlineWidth, innerColor, outerColor);
}
model = itemLayer.getModelMiddle();
if (model != null)
{
drawModelOutline(model, lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, itemLayer.getPlane()),
0, outlineWidth, innerColor, outerColor);
}
model = itemLayer.getModelTop();
if (model != null)
{
drawModelOutline(model, lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, itemLayer.getPlane()),
0, outlineWidth, innerColor, outerColor);
}
}
}
private void drawOutline(DecorativeObject decorativeObject, int outlineWidth, Color innerColor, Color outerColor)
{
LocalPoint lp = decorativeObject.getLocalLocation();
if (lp != null)
{
Model model = decorativeObject.getModel1();
if (model != null)
{
drawModelOutline(model,
lp.getX() + decorativeObject.getXOffset(),
lp.getY() + decorativeObject.getYOffset(),
Perspective.getTileHeight(client, lp, decorativeObject.getPlane()),
decorativeObject.getOrientation(), outlineWidth, innerColor, outerColor);
}
model = decorativeObject.getModel2();
if (model != null)
{
// Offset is not used for the second model
drawModelOutline(model, lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, decorativeObject.getPlane()),
decorativeObject.getOrientation(), outlineWidth, innerColor, outerColor);
}
}
}
private void drawOutline(WallObject wallObject, int outlineWidth, Color innerColor, Color outerColor)
{
LocalPoint lp = wallObject.getLocalLocation();
if (lp != null)
{
Model model = wallObject.getModelA();
if (model != null)
{
drawModelOutline(model, lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, wallObject.getPlane()),
wallObject.getOrientationA(), outlineWidth, innerColor, outerColor);
}
model = wallObject.getModelB();
if (model != null)
{
drawModelOutline(model, lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, wallObject.getPlane()),
wallObject.getOrientationB(), outlineWidth, innerColor, outerColor);
}
}
}
public void drawOutline(TileObject tileObject, int outlineWidth, Color color)
{
drawOutline(tileObject, outlineWidth, color, color);
}
public void drawOutline(TileObject tileObject,
int outlineWidth, Color innerColor, Color outerColor)
{
if (tileObject instanceof GameObject)
{
drawOutline((GameObject) tileObject, outlineWidth, innerColor, outerColor);
}
else if (tileObject instanceof GroundObject)
{
drawOutline((GroundObject) tileObject, outlineWidth, innerColor, outerColor);
}
else if (tileObject instanceof ItemLayer)
{
drawOutline((ItemLayer) tileObject, outlineWidth, innerColor, outerColor);
}
else if (tileObject instanceof DecorativeObject)
{
drawOutline((DecorativeObject) tileObject, outlineWidth, innerColor, outerColor);
}
else if (tileObject instanceof WallObject)
{
drawOutline((WallObject) tileObject, outlineWidth, innerColor, outerColor);
}
}
}

View File

@@ -64,14 +64,24 @@ public class ComparableEntry
public ComparableEntry(String option, String target, int id, int type, boolean strictOption, boolean strictTarget)
{
this.option = option;
this.target = target;
this.option = Text.standardize(option);
this.target = Text.standardize(target);
this.id = id;
this.type = type;
this.strictOption = strictOption;
this.strictTarget = strictTarget;
}
// This is only used for type checking, which is why it has everything but target
// target sometimes changes to option.
public ComparableEntry(MenuEntry e)
{
this.option = Text.standardize(e.getOption());
this.id = e.getIdentifier();
this.type = e.getType();
this.strictOption = true;
}
boolean matches(MenuEntry entry)
{
String opt = Text.standardize(entry.getOption());

Some files were not shown because too many files have changed in this diff Show More