@@ -129,7 +129,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 +148,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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +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.util.UUID;
|
||||
|
||||
public class State
|
||||
{
|
||||
private UUID uuid;
|
||||
private String apiVersion;
|
||||
|
||||
public UUID getUuid()
|
||||
{
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid)
|
||||
{
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getApiVersion()
|
||||
{
|
||||
return apiVersion;
|
||||
}
|
||||
|
||||
public void setApiVersion(String apiVersion)
|
||||
{
|
||||
this.apiVersion = apiVersion;
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.http.service.account;
|
||||
|
||||
public class UserInfo
|
||||
{
|
||||
private String email;
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "UserInfo{" + "email=" + email + '}';
|
||||
}
|
||||
|
||||
public String getEmail()
|
||||
{
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email)
|
||||
{
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +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.beans;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SessionEntry
|
||||
{
|
||||
private int user;
|
||||
private UUID uuid;
|
||||
private Instant created;
|
||||
private Instant lastUsed;
|
||||
|
||||
public int getUser()
|
||||
{
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(int user)
|
||||
{
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public UUID getUuid()
|
||||
{
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid)
|
||||
{
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public Instant getCreated()
|
||||
{
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Instant created)
|
||||
{
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public Instant getLastUsed()
|
||||
{
|
||||
return lastUsed;
|
||||
}
|
||||
|
||||
public void setLastUsed(Instant lastUsed)
|
||||
{
|
||||
this.lastUsed = lastUsed;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +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.beans;
|
||||
|
||||
public class UserEntry
|
||||
{
|
||||
private int id;
|
||||
private String username;
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "UserEntry{" + "id=" + id + ", username=" + username + '}';
|
||||
}
|
||||
|
||||
public int getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username)
|
||||
{
|
||||
this.username = username;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.cache.beans;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ArchiveEntry
|
||||
{
|
||||
private int id;
|
||||
private int archiveId;
|
||||
private int nameHash;
|
||||
private int crc;
|
||||
private int revision;
|
||||
private byte[] hash;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,180 +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.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 net.runelite.http.service.util.exception.NotFoundException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/chat")
|
||||
public class ChatController
|
||||
{
|
||||
private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]");
|
||||
private static final int STRING_MAX_LENGTH = 50;
|
||||
|
||||
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)
|
||||
{
|
||||
if (kc <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
chatService.setKc(name, boss, kc);
|
||||
killCountCache.put(new KillCountKey(name, boss), kc);
|
||||
}
|
||||
|
||||
@GetMapping("/kc")
|
||||
public int getKc(@RequestParam String name, @RequestParam String boss)
|
||||
{
|
||||
Integer kc = killCountCache.getIfPresent(new KillCountKey(name, boss));
|
||||
if (kc == null)
|
||||
{
|
||||
kc = chatService.getKc(name, boss);
|
||||
if (kc != null)
|
||||
{
|
||||
killCountCache.put(new KillCountKey(name, boss), kc);
|
||||
}
|
||||
}
|
||||
|
||||
if (kc == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return kc;
|
||||
}
|
||||
|
||||
@PostMapping("/qp")
|
||||
public void submitQp(@RequestParam String name, @RequestParam int qp)
|
||||
{
|
||||
if (qp < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
chatService.setQp(name, qp);
|
||||
}
|
||||
|
||||
@GetMapping("/qp")
|
||||
public int getQp(@RequestParam String name)
|
||||
{
|
||||
Integer kc = chatService.getQp(name);
|
||||
if (kc == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return kc;
|
||||
}
|
||||
|
||||
@PostMapping("/gc")
|
||||
public void submitGc(@RequestParam String name, @RequestParam int gc)
|
||||
{
|
||||
if (gc < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
chatService.setGc(name, gc);
|
||||
}
|
||||
|
||||
@GetMapping("/gc")
|
||||
public int getKc(@RequestParam String name)
|
||||
{
|
||||
Integer 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;
|
||||
}
|
||||
}
|
||||
@@ -1,160 +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.chat;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import net.runelite.http.api.chat.Task;
|
||||
import net.runelite.http.service.util.redis.RedisPool;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
@Service
|
||||
public class ChatService
|
||||
{
|
||||
private static final Duration EXPIRE = Duration.ofMinutes(2);
|
||||
|
||||
private final RedisPool jedisPool;
|
||||
|
||||
@Autowired
|
||||
public ChatService(RedisPool jedisPool)
|
||||
{
|
||||
this.jedisPool = jedisPool;
|
||||
}
|
||||
|
||||
public Integer getKc(String name, String boss)
|
||||
{
|
||||
String value;
|
||||
try (Jedis jedis = jedisPool.getResource())
|
||||
{
|
||||
value = jedis.get("kc." + name + "." + boss);
|
||||
}
|
||||
return value == null ? null : Integer.parseInt(value);
|
||||
}
|
||||
|
||||
public void setKc(String name, String boss, int kc)
|
||||
{
|
||||
try (Jedis jedis = jedisPool.getResource())
|
||||
{
|
||||
jedis.setex("kc." + name + "." + boss, (int) EXPIRE.getSeconds(), Integer.toString(kc));
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getQp(String name)
|
||||
{
|
||||
String value;
|
||||
try (Jedis jedis = jedisPool.getResource())
|
||||
{
|
||||
value = jedis.get("qp." + name);
|
||||
}
|
||||
return value == null ? null : Integer.parseInt(value);
|
||||
}
|
||||
|
||||
public void setQp(String name, int qp)
|
||||
{
|
||||
try (Jedis jedis = jedisPool.getResource())
|
||||
{
|
||||
jedis.setex("qp." + name, (int) EXPIRE.getSeconds(), Integer.toString(qp));
|
||||
}
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
if (map.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Task task = new Task();
|
||||
task.setTask(map.get("task"));
|
||||
task.setAmount(Integer.parseInt(map.get("amount")));
|
||||
task.setInitialAmount(Integer.parseInt(map.get("initialAmount")));
|
||||
task.setLocation(map.get("location"));
|
||||
return task;
|
||||
}
|
||||
|
||||
public void setTask(String name, Task task)
|
||||
{
|
||||
Map<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())
|
||||
{
|
||||
jedis.hmset(key, taskMap);
|
||||
jedis.expire(key, (int) EXPIRE.getSeconds());
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getPb(String name, String boss)
|
||||
{
|
||||
String value;
|
||||
try (Jedis jedis = jedisPool.getResource())
|
||||
{
|
||||
value = jedis.get("pb." + boss + "." + name);
|
||||
}
|
||||
return value == null ? null : Integer.parseInt(value);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +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.chat;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
class KillCountKey
|
||||
{
|
||||
private String username;
|
||||
private String boss;
|
||||
}
|
||||
@@ -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.examine;
|
||||
|
||||
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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/examine")
|
||||
public class ExamineController
|
||||
{
|
||||
private final ExamineService examineService;
|
||||
private final ItemService itemService;
|
||||
|
||||
@Autowired
|
||||
public ExamineController(ExamineService examineService, ItemService itemService)
|
||||
{
|
||||
this.examineService = examineService;
|
||||
this.itemService = itemService;
|
||||
}
|
||||
|
||||
@GetMapping("/npc/{id}")
|
||||
public String getNpc(@PathVariable int id)
|
||||
{
|
||||
return examineService.get(NPC, id);
|
||||
}
|
||||
|
||||
@GetMapping("/object/{id}")
|
||||
public String getObject(@PathVariable int id)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return item.getDescription();
|
||||
}
|
||||
|
||||
return examineService.get(ITEM, id);
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/npc/{id}", method = POST)
|
||||
public void submitNpc(@PathVariable int id, @RequestBody String examine)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +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.examine;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class ExamineEntry
|
||||
{
|
||||
private ExamineType type;
|
||||
private int id;
|
||||
private Instant time;
|
||||
private int count;
|
||||
private String text;
|
||||
|
||||
public ExamineType getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ExamineType type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Instant getTime()
|
||||
{
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(Instant time)
|
||||
{
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public int getCount()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(int count)
|
||||
{
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public String getText()
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +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.examine;
|
||||
|
||||
public enum ExamineType
|
||||
{
|
||||
OBJECT,
|
||||
NPC,
|
||||
ITEM;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +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 lombok.Data;
|
||||
|
||||
@Data
|
||||
class TwitterStatusesResponseItem
|
||||
{
|
||||
private long id;
|
||||
private String text;
|
||||
private TwitterStatusesResponseItemUser user;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +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;
|
||||
|
||||
enum TradeAction
|
||||
{
|
||||
BUY,
|
||||
SELL;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +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;
|
||||
|
||||
@Data
|
||||
public class RSItemResponse
|
||||
{
|
||||
private RSItem item;
|
||||
}
|
||||
@@ -1,37 +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.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RSPrices
|
||||
{
|
||||
/**
|
||||
* unix time in ms to price in gp
|
||||
*/
|
||||
private Map<Long, Integer> daily;
|
||||
}
|
||||
@@ -1,34 +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.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RSSearch
|
||||
{
|
||||
private List<RSItem> items;
|
||||
}
|
||||
@@ -1,40 +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.loottracker;
|
||||
|
||||
import java.time.Instant;
|
||||
import lombok.Data;
|
||||
import net.runelite.http.api.loottracker.LootRecordType;
|
||||
|
||||
@Data
|
||||
class LootResult
|
||||
{
|
||||
private int killId;
|
||||
private Instant time;
|
||||
private LootRecordType type;
|
||||
private String eventId;
|
||||
private int itemId;
|
||||
private int itemQuantity;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -35,4 +35,4 @@ public class InternalServerErrorException extends RuntimeException
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,4 @@ import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
public class NotFoundException extends RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -61,7 +53,6 @@ public class XteaService
|
||||
+ ") ENGINE=InnoDB";
|
||||
|
||||
private final Sql2o sql2o;
|
||||
private final CacheService cacheService;
|
||||
|
||||
private final Cache<Integer, XteaCache> keyCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(1024)
|
||||
@@ -69,12 +60,10 @@ public class XteaService
|
||||
|
||||
@Autowired
|
||||
public XteaService(
|
||||
@Qualifier("Runelite SQL2O") Sql2o sql2o,
|
||||
CacheService cacheService
|
||||
@Qualifier("Runelite SQL2O") Sql2o sql2o
|
||||
)
|
||||
{
|
||||
this.sql2o = sql2o;
|
||||
this.cacheService = cacheService;
|
||||
|
||||
try (Connection con = sql2o.beginTransaction())
|
||||
{
|
||||
@@ -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,6 +124,7 @@ 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]
|
||||
@@ -151,10 +134,6 @@ public class XteaService
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!checkKeys(cache, region, keys))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (query == null)
|
||||
{
|
||||
@@ -201,40 +180,4 @@ public class XteaService
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user