http-services(-pro): Add back http-service and move custom http-service

This commit is contained in:
Owain van Brakel
2019-08-09 05:22:26 +02:00
parent f85b1254cd
commit 3a92e3b008
109 changed files with 8699 additions and 112 deletions

View File

@@ -0,0 +1,203 @@
/*
* 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;
import ch.qos.logback.classic.LoggerContext;
import com.google.common.base.Strings;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.service.util.InstantConverter;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import org.slf4j.ILoggerFactory;
import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.sql2o.Sql2o;
import org.sql2o.converters.Converter;
import org.sql2o.quirks.NoQuirks;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableScheduling
@Slf4j
public class SpringBootWebApplication extends SpringBootServletInitializer
{
@Bean
protected ServletContextListener listener()
{
return new ServletContextListener()
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
log.info("RuneLitePlus API started");
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
// Destroy okhttp client
OkHttpClient client = RuneLiteAPI.CLIENT;
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
try
{
Cache cache = client.cache();
if (cache != null)
{
cache.close();
}
}
catch (IOException ex)
{
log.warn(null, ex);
}
log.info("RuneLitePlus API stopped");
}
};
}
@ConfigurationProperties(prefix = "datasource.runeliteplus")
@Bean("dataSourceRuneLite")
public DataSourceProperties dataSourceProperties()
{
return new DataSourceProperties();
}
@ConfigurationProperties(prefix = "datasource.runeliteplus-cache")
@Bean("dataSourceRuneLiteCache")
public DataSourceProperties dataSourcePropertiesCache()
{
return new DataSourceProperties();
}
@ConfigurationProperties(prefix = "datasource.runeliteplus-tracker")
@Bean("dataSourceRuneLiteTracker")
public DataSourceProperties dataSourcePropertiesTracker()
{
return new DataSourceProperties();
}
@Bean(value = "runelite", destroyMethod = "")
public DataSource runeliteDataSource(@Qualifier("dataSourceRuneLite") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean(value = "runelite-cache2", destroyMethod = "")
public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean(value = "runelite-tracker", destroyMethod = "")
public DataSource runeliteTrackerDataSource(@Qualifier("dataSourceRuneLiteTracker") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean("Runelite SQL2O")
public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource)
{
return createSql2oFromDataSource(dataSource);
}
@Bean("Runelite Cache SQL2O")
public Sql2o cacheSql2o(@Qualifier("runelite-cache2") DataSource dataSource)
{
return createSql2oFromDataSource(dataSource);
}
@Bean("Runelite XP Tracker SQL2O")
public Sql2o trackerSql2o(@Qualifier("runelite-tracker") DataSource dataSource)
{
return createSql2oFromDataSource(dataSource);
}
private static DataSource getDataSource(DataSourceProperties dataSourceProperties)
{
if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName()))
{
// Use JNDI provided datasource, which is already configured with pooling
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(dataSourceProperties.getJndiName());
}
else
{
return dataSourceProperties.initializeDataSourceBuilder().build();
}
}
private static Sql2o createSql2oFromDataSource(final DataSource dataSource)
{
final Map<Class, Converter> converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters));
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
{
return application.sources(SpringBootWebApplication.class);
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException
{
super.onStartup(servletContext);
ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory();
if (loggerFactory instanceof LoggerContext)
{
LoggerContext loggerContext = (LoggerContext) loggerFactory;
loggerContext.setPackagingDataEnabled(false);
log.debug("Disabling logback packaging data");
}
}
public static void main(String[] args)
{
SpringApplication.run(SpringBootWebApplication.class, args);
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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;
import java.util.List;
import net.runelite.http.api.RuneLiteAPI;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class SpringWebMvcConfigurer implements WebMvcConfigurer
{
/**
* Configure .js as application/json to trick Cloudflare into caching json responses
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
configurer.mediaType("js", MediaType.APPLICATION_JSON);
}
/**
* Use GSON instead of Jackson for JSON serialization
* @param converters
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters)
{
// Could not figure out a better way to force GSON
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
gsonHttpMessageConverter.setGson(RuneLiteAPI.GSON);
converters.add(gsonHttpMessageConverter);
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.animation;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
class AnimationCache
{
private int npcid;
private int anim1;
private int anim2;
private int anim3;
private int anim4;
private int anim5;
private int anim6;
private int anim7;
private int anim8;
private int anim9;
private int anim10;
}

View File

@@ -0,0 +1,92 @@
/*
* 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.animation;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.http.api.animation.AnimationKey;
import net.runelite.http.api.animation.AnimationRequest;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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("/animation")
public class AnimationController
{
@Autowired
private AnimationEndpoint animationService;
@RequestMapping(method = POST)
public void submit(@RequestBody AnimationRequest animationRequest)
{
animationService.submit(animationRequest);
}
@GetMapping
public List<AnimationKey> get()
{
return animationService.get().stream()
.map(AnimationController::entryToKey)
.collect(Collectors.toList());
}
@GetMapping("/{npcid}")
public AnimationKey getRegion(@PathVariable int npcid)
{
AnimationEntry animationEntry = animationService.getNPC(npcid);
if (animationEntry == null)
{
throw new NotFoundException();
}
return entryToKey(animationEntry);
}
private static AnimationKey entryToKey(AnimationEntry xe)
{
AnimationKey animationKey = new AnimationKey();
animationKey.setNPCId(xe.getNPCId());
animationKey.setAnimations(new int[]
{
xe.getAnimations()[0],
xe.getAnimations()[1],
xe.getAnimations()[2],
xe.getAnimations()[3],
xe.getAnimations()[4],
xe.getAnimations()[5],
xe.getAnimations()[6],
xe.getAnimations()[7],
xe.getAnimations()[8],
xe.getAnimations()[9],
});
return animationKey;
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.animation;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.List;
import net.runelite.http.api.animation.AnimationKey;
import net.runelite.http.api.animation.AnimationRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
public class AnimationEndpoint
{
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `animation` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `npcid` int(11) NOT NULL,\n"
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " `rev` int(11) NOT NULL,\n"
+ " `anim1` int(11) NOT NULL,\n"
+ " `anim2` int(11),\n"
+ " `anim3` int(11),\n"
+ " `anim4` int(11),\n"
+ " `anim5` int(11),\n"
+ " `anim6` int(11),\n"
+ " `anim7` int(11),\n"
+ " `anim8` int(11),\n"
+ " `anim9` int(11),\n"
+ " `anim10` int(11),\n"
+ " PRIMARY KEY (`id`),\n"
+ " KEY `npcid` (`npcid`,`time`)\n"
+ ") ENGINE=InnoDB";
private final Sql2o sql2o;
private final Cache<Integer, AnimationCache> keyCache = CacheBuilder.newBuilder()
.maximumSize(1024)
.build();
@Autowired
public AnimationEndpoint(
@Qualifier("Runelite SQL2O") Sql2o sql2o
)
{
this.sql2o = sql2o;
try (Connection con = sql2o.beginTransaction())
{
con.createQuery(CREATE_SQL)
.executeUpdate();
}
}
private AnimationEntry findLatestAnimations(Connection con, int npcid)
{
return con.createQuery("select npcid, time, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation "
+ "where npcid = :npcid "
+ "order by time desc "
+ "limit 1")
.addParameter("npcid", npcid)
.executeAndFetchFirst(AnimationEntry.class);
}
public void submit(AnimationRequest animationRequest)
{
boolean cached = true;
for (AnimationKey key : animationRequest.getKeys())
{
int npcid = key.getNPCId();
int[] animations = key.getAnimations();
AnimationCache animationCache = keyCache.getIfPresent(npcid);
if (animationCache == null
|| animationCache.getAnim1() != animations[0]
|| animationCache.getAnim2() != animations[1]
|| animationCache.getAnim3() != animations[2]
|| animationCache.getAnim4() != animations[3]
|| animationCache.getAnim5() != animations[4]
|| animationCache.getAnim6() != animations[5]
|| animationCache.getAnim7() != animations[6]
|| animationCache.getAnim8() != animations[7]
|| animationCache.getAnim9() != animations[8]
|| animationCache.getAnim10() != animations[9])
{
cached = false;
keyCache.put(npcid, new AnimationCache(npcid, animations[0], animations[1], animations[2], animations[3], animations[4], animations[5], animations[6], animations[7], animations[8], animations[9]));
}
}
if (cached)
{
return;
}
try (Connection con = sql2o.beginTransaction())
{
Query query = null;
for (AnimationKey key : animationRequest.getKeys())
{
int npcid = key.getNPCId();
int[] animations = key.getAnimations();
AnimationEntry animationEntry = findLatestAnimations(con, npcid);
if (animations.length != 10)
{
throw new IllegalArgumentException("Key length must be 10");
}
// already have these?
if (animationEntry != null
&& animationEntry.getAnimations()[0] == animations[0]
&& animationEntry.getAnimations()[1] == animations[1]
&& animationEntry.getAnimations()[2] == animations[2]
&& animationEntry.getAnimations()[3] == animations[3]
&& animationEntry.getAnimations()[4] == animations[4]
&& animationEntry.getAnimations()[5] == animations[5]
&& animationEntry.getAnimations()[6] == animations[6]
&& animationEntry.getAnimations()[7] == animations[7]
&& animationEntry.getAnimations()[8] == animations[8]
&& animationEntry.getAnimations()[9] == animations[9])
{
continue;
}
if (query == null)
{
query = con.createQuery("insert into animation (npcid, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10) "
+ "values (:npcid, :rev, :anim1, :anim2, :anim3, :anim4, anim5, anim6, anim7, anim8, anim9, anim10)");
}
query.addParameter("npcid", npcid)
.addParameter("rev", animationRequest.getRevision())
.addParameter("anim1", animations[0])
.addParameter("anim2", animations[1])
.addParameter("anim3", animations[2])
.addParameter("anim4", animations[3])
.addParameter("anim5", animations[4])
.addParameter("anim6", animations[5])
.addParameter("anim7", animations[6])
.addParameter("anim8", animations[7])
.addParameter("anim9", animations[8])
.addParameter("anim10", animations[9])
.addToBatch();
}
if (query != null)
{
query.executeBatch();
con.commit(false);
}
}
}
public List<AnimationEntry> get()
{
try (Connection con = sql2o.open())
{
return con.createQuery(
"select t1.npcid, t2.time, t2.rev, t2.anim1, t2.anim2, t2.anim3, t2.anim4, t2.anim5, t2.anim6, t2.anim7, t2.anim8, t2.anim9, t2.anim10 from " +
"(select npcid,max(id) as id from animation group by npcid) t1 " +
"join animation t2 on t1.id = t2.id")
.executeAndFetch(AnimationEntry.class);
}
}
public AnimationEntry getNPC(int npcid)
{
try (Connection con = sql2o.open())
{
return con.createQuery("select npcid, time, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation "
+ "where npcid = :npcid order by time desc limit 1")
.addParameter("npcid", npcid)
.executeAndFetchFirst(AnimationEntry.class);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.animation;
import java.time.Instant;
public class AnimationEntry
{
private int npcid;
private Instant time;
private int rev;
private int[] animations;
public int getNPCId()
{
return npcid;
}
public void setNPCId(int npcid)
{
this.npcid = npcid;
}
public Instant getTime()
{
return time;
}
public void setTime(Instant time)
{
this.time = time;
}
public int getRev()
{
return rev;
}
public void setRev(int rev)
{
this.rev = rev;
}
public int[] getAnimations()
{
return animations;
}
public void setAnimations(int[] animations)
{
this.animations = animations;
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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 java.util.regex.Pattern;
import com.google.common.base.Strings;
import net.runelite.http.api.chat.House;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
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;
@Autowired
private ChatService chatService;
@PostMapping("/layout")
public void submitLayout(@RequestParam String name, @RequestParam String layout)
{
if (Strings.isNullOrEmpty(layout))
{
return;
}
chatService.setLayout(name, layout);
}
@GetMapping("/layout")
public String getLayout(@RequestParam String name)
{
String layout = chatService.getLayout(name);
if (layout == null)
{
throw new NotFoundException();
}
return layout;
}
@PostMapping("/hosts")
public void submitHost(@RequestParam int world, @RequestParam String location, @RequestParam String owner, @RequestParam boolean guildedAltar, @RequestParam boolean occultAltar, @RequestParam boolean spiritTree, @RequestParam boolean fairyRing, @RequestParam boolean wildernessObelisk, @RequestParam boolean repairStand, @RequestParam boolean combatDummy, @RequestParam(required = false, defaultValue = "false") boolean remove)
{
if (!location.equals("Rimmington") && !location.equals("Yanille"))
{
return;
}
House house = new House();
house.setOwner(owner);
house.setGuildedAltarPresent(guildedAltar);
house.setOccultAltarPresent(occultAltar);
house.setSpiritTreePresent(spiritTree);
house.setFairyRingPresent(fairyRing);
house.setWildernessObeliskPresent(wildernessObelisk);
house.setRepairStandPresent(repairStand);
house.setCombatDummyPresent(combatDummy);
if (remove)
{
chatService.removeHost(world, location, house);
}
else
{
chatService.addHost(world, location, house);
}
}
@GetMapping("/hosts")
public House[] getHosts(@RequestParam int world, @RequestParam String location)
{
return chatService.getHosts(world, location);
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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 java.time.Duration;
import java.util.List;
import net.runelite.http.api.chat.ChatClient;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.chat.House;
import net.runelite.http.service.util.redis.RedisPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
@Service
public class ChatService
{
private static final Duration EXPIRE = Duration.ofMinutes(2);
private final RedisPool jedisPool;
private final ChatClient chatClient = new ChatClient();
@Autowired
public ChatService(RedisPool jedisPool)
{
this.jedisPool = jedisPool;
}
public String getLayout(String name)
{
String value;
try (Jedis jedis = jedisPool.getResource())
{
value = jedis.get("layout." + name);
}
return value;
}
public void setLayout(String name, String layout)
{
if (!chatClient.testLayout(layout))
{
throw new IllegalArgumentException(layout);
}
try (Jedis jedis = jedisPool.getResource())
{
jedis.setex("layout." + name, (int) EXPIRE.getSeconds(), layout);
}
}
public void addHost(int world, String location, House house)
{
String houseJSON = house.toString();
String key = "hosts.w" + Integer.toString(world) + "." + location;
try (Jedis jedis = jedisPool.getResource())
{
jedis.rpush(key, houseJSON);
}
}
public House[] getHosts(int world, String location)
{
List<String> json;
String key = "hosts.w" + Integer.toString(world) + "." + location;
try (Jedis jedis = jedisPool.getResource())
{
json = jedis.lrange(key, 0, 25);
}
if (json.isEmpty())
{
return null;
}
House[] hosts = new House[json.size()];
for (int i = 0; i < json.size(); i++)
{
hosts[i] = RuneLiteAPI.GSON.fromJson(json.get(i), House.class);
}
return hosts;
}
public void removeHost(int world, String location, House house)
{
String json = house.toString();
String key = "hosts.w" + Integer.toString(world) + "." + location;
try (Jedis jedis = jedisPool.getResource())
{
jedis.lrem(key, 0, json);
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.xtea;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
class XteaCache
{
private int region;
private int key1;
private int key2;
private int key3;
private int key4;
}

View File

@@ -0,0 +1,86 @@
/*
* 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.xtea;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.http.api.xtea.XteaKey;
import net.runelite.http.api.xtea.XteaRequest;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/xtea")
public class XteaController
{
@Autowired
private XteaEndpoint xteaService;
@RequestMapping(method = POST)
public void submit(@RequestBody XteaRequest xteaRequest)
{
xteaService.submit(xteaRequest);
}
@GetMapping
public List<XteaKey> get()
{
return xteaService.get().stream()
.map(XteaController::entryToKey)
.collect(Collectors.toList());
}
@GetMapping("/{region}")
public XteaKey getRegion(@PathVariable int region)
{
XteaEntry xteaRegion = xteaService.getRegion(region);
if (xteaRegion == null)
{
throw new NotFoundException();
}
return entryToKey(xteaRegion);
}
private static XteaKey entryToKey(XteaEntry xe)
{
XteaKey xteaKey = new XteaKey();
xteaKey.setRegion(xe.getRegion());
xteaKey.setKeys(new int[]
{
xe.getKey1(),
xe.getKey2(),
xe.getKey3(),
xe.getKey4()
});
return xteaKey;
}
}

View File

@@ -0,0 +1,183 @@
/*
* 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.xtea;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.List;
import net.runelite.http.api.xtea.XteaKey;
import net.runelite.http.api.xtea.XteaRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
public class XteaEndpoint
{
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `region` int(11) NOT NULL,\n"
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " `rev` int(11) NOT NULL,\n"
+ " `key1` int(11) NOT NULL,\n"
+ " `key2` int(11) NOT NULL,\n"
+ " `key3` int(11) NOT NULL,\n"
+ " `key4` int(11) NOT NULL,\n"
+ " PRIMARY KEY (`id`),\n"
+ " KEY `region` (`region`,`time`)\n"
+ ") ENGINE=InnoDB";
private final Sql2o sql2o;
private final Cache<Integer, XteaCache> keyCache = CacheBuilder.newBuilder()
.maximumSize(1024)
.build();
@Autowired
public XteaEndpoint(
@Qualifier("Runelite SQL2O") Sql2o sql2o
)
{
this.sql2o = sql2o;
try (Connection con = sql2o.beginTransaction())
{
con.createQuery(CREATE_SQL)
.executeUpdate();
}
}
private XteaEntry findLatestXtea(Connection con, int region)
{
return con.createQuery("select region, time, key1, key2, key3, key4 from xtea "
+ "where region = :region "
+ "order by time desc "
+ "limit 1")
.addParameter("region", region)
.executeAndFetchFirst(XteaEntry.class);
}
public void submit(XteaRequest xteaRequest)
{
boolean cached = true;
for (XteaKey key : xteaRequest.getKeys())
{
int region = key.getRegion();
int[] keys = key.getKeys();
XteaCache xteaCache = keyCache.getIfPresent(region);
if (xteaCache == null
|| xteaCache.getKey1() != keys[0]
|| xteaCache.getKey2() != keys[1]
|| xteaCache.getKey3() != keys[2]
|| xteaCache.getKey4() != keys[3])
{
cached = false;
keyCache.put(region, new XteaCache(region, keys[0], keys[1], keys[2], keys[3]));
}
}
if (cached)
{
return;
}
try (Connection con = sql2o.beginTransaction())
{
Query query = null;
for (XteaKey key : xteaRequest.getKeys())
{
int region = key.getRegion();
int[] keys = key.getKeys();
XteaEntry xteaEntry = findLatestXtea(con, region);
if (keys.length != 4)
{
throw new IllegalArgumentException("Key length must be 4");
}
// already have these?
// TODO : check if useful / works should check with findLatestXtea
if (xteaEntry != null
&& xteaEntry.getKey1() == keys[0]
&& xteaEntry.getKey2() == keys[1]
&& xteaEntry.getKey3() == keys[2]
&& xteaEntry.getKey4() == keys[3])
{
continue;
}
if (query == null)
{
query = con.createQuery("insert into xtea (region, rev, key1, key2, key3, key4) "
+ "values (:region, :rev, :key1, :key2, :key3, :key4)");
}
query.addParameter("region", region)
.addParameter("rev", xteaRequest.getRevision())
.addParameter("key1", keys[0])
.addParameter("key2", keys[1])
.addParameter("key3", keys[2])
.addParameter("key4", keys[3])
.addToBatch();
}
if (query != null)
{
query.executeBatch();
con.commit(false);
}
}
}
public List<XteaEntry> get()
{
try (Connection con = sql2o.open())
{
return con.createQuery(
"select t1.region, t2.time, t2.rev, t2.key1, t2.key2, t2.key3, t2.key4 from " +
"(select region,max(id) as id from xtea group by region) t1 " +
"join xtea t2 on t1.id = t2.id")
.executeAndFetch(XteaEntry.class);
}
}
public XteaEntry getRegion(int region)
{
try (Connection con = sql2o.open())
{
return con.createQuery("select region, time, rev, key1, key2, key3, key4 from xtea "
+ "where region = :region order by time desc limit 1")
.addParameter("region", region)
.executeAndFetchFirst(XteaEntry.class);
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.xtea;
import java.time.Instant;
public class XteaEntry
{
private int region;
private Instant time;
private int rev;
private int key1;
private int key2;
private int key3;
private int key4;
public int getRegion()
{
return region;
}
public void setRegion(int region)
{
this.region = region;
}
public Instant getTime()
{
return time;
}
public void setTime(Instant time)
{
this.time = time;
}
public int getRev()
{
return rev;
}
public void setRev(int rev)
{
this.rev = rev;
}
public int getKey1()
{
return key1;
}
public void setKey1(int key1)
{
this.key1 = key1;
}
public int getKey2()
{
return key2;
}
public void setKey2(int key2)
{
this.key2 = key2;
}
public int getKey3()
{
return key3;
}
public void setKey3(int key3)
{
this.key3 = key3;
}
public int getKey4()
{
return key4;
}
public void setKey4(int key4)
{
this.key4 = key4;
}
}

View File

@@ -0,0 +1,31 @@
# Enable debug logging
debug: true
logging.level.net.runelite: DEBUG
# Development data sources
datasource:
runelite:
jndiName:
driverClassName: org.mariadb.jdbc.Driver
type: org.mariadb.jdbc.MariaDbDataSource
url: jdbc:mariadb://localhost:3306/runelite
username: runelite
password: runelite
runelite-cache:
jndiName:
driverClassName: org.mariadb.jdbc.Driver
type: org.mariadb.jdbc.MariaDbDataSource
url: jdbc:mariadb://localhost:3306/cache
username: runelite
password: runelite
runelite-tracker:
jndiName:
driverClassName: org.mariadb.jdbc.Driver
type: org.mariadb.jdbc.MariaDbDataSource
url: jdbc:mariadb://localhost:3306/xptracker
username: runelite
password: runelite
# Development oauth callback (without proxy)
oauth:
callback: http://localhost:8080/account/callback

View File

@@ -0,0 +1,39 @@
datasource:
runeliteplus:
jndiName: java:comp/env/jdbc/runelite
runeliteplus-cache:
jndiName: java:comp/env/jdbc/runelite-cache2
runeliteplus-tracker:
jndiName: java:comp/env/jdbc/runelite-tracker
# By default Spring tries to register the datasource as an MXBean,
# so if multiple apis are deployed on one web container with
# shared datasource it tries to register it multiples times and
# fails when starting the 2nd api
spring.jmx.enabled: false
# Google OAuth client
oauth:
client-id:
client-secret:
callback: https://api.runelite.net/oauth/
# Minio client storage for cache
minio:
endpoint: http://localhost:9000
accesskey: AM54M27O4WZK65N6F8IP
secretkey: /PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP
bucket: runelite
# Redis client for temporary data storage
redis:
pool.size: 10
host: http://localhost:6379
# Twitter client for feed
runelite:
twitter:
consumerkey:
secretkey:
listid: 968949795153948673

View File

@@ -0,0 +1,135 @@
-- MySQL dump 10.16 Distrib 10.2.18-MariaDB, for Linux (x86_64)
--
-- Host: localhost Database: xptracker
-- ------------------------------------------------------
-- Server version 10.2.18-MariaDB
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `player`
--
DROP TABLE IF EXISTS `player`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `player` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`tracked_since` timestamp NOT NULL DEFAULT current_timestamp(),
`last_updated` timestamp NOT NULL DEFAULT current_timestamp(),
`rank` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `xp`
--
DROP TABLE IF EXISTS `xp`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `xp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`time` timestamp NOT NULL DEFAULT current_timestamp(),
`player` int(11) NOT NULL,
`attack_xp` int(11) NOT NULL,
`defence_xp` int(11) NOT NULL,
`strength_xp` int(11) NOT NULL,
`hitpoints_xp` int(11) NOT NULL,
`ranged_xp` int(11) NOT NULL,
`prayer_xp` int(11) NOT NULL,
`magic_xp` int(11) NOT NULL,
`cooking_xp` int(11) NOT NULL,
`woodcutting_xp` int(11) NOT NULL,
`fletching_xp` int(11) NOT NULL,
`fishing_xp` int(11) NOT NULL,
`firemaking_xp` int(11) NOT NULL,
`crafting_xp` int(11) NOT NULL,
`smithing_xp` int(11) NOT NULL,
`mining_xp` int(11) NOT NULL,
`herblore_xp` int(11) NOT NULL,
`agility_xp` int(11) NOT NULL,
`thieving_xp` int(11) NOT NULL,
`slayer_xp` int(11) NOT NULL,
`farming_xp` int(11) NOT NULL,
`runecraft_xp` int(11) NOT NULL,
`hunter_xp` int(11) NOT NULL,
`construction_xp` int(11) NOT NULL,
`overall_xp` int(11) GENERATED ALWAYS AS (`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`) VIRTUAL,
`attack_level` int(11) GENERATED ALWAYS AS (level_for_xp(`attack_xp` AS `attack_xp`)) VIRTUAL,
`defence_level` int(11) GENERATED ALWAYS AS (level_for_xp(`defence_xp` AS `defence_xp`)) VIRTUAL,
`strength_level` int(11) GENERATED ALWAYS AS (level_for_xp(`strength_xp` AS `strength_xp`)) VIRTUAL,
`hitpoints_level` int(11) GENERATED ALWAYS AS (level_for_xp(`hitpoints_xp` AS `hitpoints_xp`)) VIRTUAL,
`ranged_level` int(11) GENERATED ALWAYS AS (level_for_xp(`ranged_xp` AS `ranged_xp`)) VIRTUAL,
`prayer_level` int(11) GENERATED ALWAYS AS (level_for_xp(`prayer_xp` AS `prayer_xp`)) VIRTUAL,
`magic_level` int(11) GENERATED ALWAYS AS (level_for_xp(`magic_xp` AS `magic_xp`)) VIRTUAL,
`cooking_level` int(11) GENERATED ALWAYS AS (level_for_xp(`cooking_xp` AS `cooking_xp`)) VIRTUAL,
`woodcutting_level` int(11) GENERATED ALWAYS AS (level_for_xp(`woodcutting_xp` AS `woodcutting_xp`)) VIRTUAL,
`fletching_level` int(11) GENERATED ALWAYS AS (level_for_xp(`fletching_xp` AS `fletching_xp`)) VIRTUAL,
`fishing_level` int(11) GENERATED ALWAYS AS (level_for_xp(`fishing_xp` AS `fishing_xp`)) VIRTUAL,
`firemaking_level` int(11) GENERATED ALWAYS AS (level_for_xp(`firemaking_xp` AS `firemaking_xp`)) VIRTUAL,
`crafting_level` int(11) GENERATED ALWAYS AS (level_for_xp(`crafting_xp` AS `crafting_xp`)) VIRTUAL,
`smithing_level` int(11) GENERATED ALWAYS AS (level_for_xp(`smithing_xp` AS `smithing_xp`)) VIRTUAL,
`mining_level` int(11) GENERATED ALWAYS AS (level_for_xp(`mining_xp` AS `mining_xp`)) VIRTUAL,
`herblore_level` int(11) GENERATED ALWAYS AS (level_for_xp(`herblore_xp` AS `herblore_xp`)) VIRTUAL,
`agility_level` int(11) GENERATED ALWAYS AS (level_for_xp(`agility_xp` AS `agility_xp`)) VIRTUAL,
`thieving_level` int(11) GENERATED ALWAYS AS (level_for_xp(`thieving_xp` AS `thieving_xp`)) VIRTUAL,
`slayer_level` int(11) GENERATED ALWAYS AS (level_for_xp(`slayer_xp` AS `slayer_xp`)) VIRTUAL,
`farming_level` int(11) GENERATED ALWAYS AS (level_for_xp(`farming_xp` AS `farming_xp`)) VIRTUAL,
`runecraft_level` int(11) GENERATED ALWAYS AS (level_for_xp(`runecraft_xp` AS `runecraft_xp`)) VIRTUAL,
`hunter_level` int(11) GENERATED ALWAYS AS (level_for_xp(`hunter_xp` AS `hunter_xp`)) VIRTUAL,
`construction_level` int(11) GENERATED ALWAYS AS (level_for_xp(`construction_xp` AS `construction_xp`)) VIRTUAL,
`overall_level` int(11) GENERATED ALWAYS AS (`attack_level` + `defence_level` + `strength_level` + `hitpoints_level` + `ranged_level` + `prayer_level` + `magic_level` + `cooking_level` + `woodcutting_level` + `fletching_level` + `fishing_level` + `firemaking_level` + `crafting_level` + `smithing_level` + `mining_level` + `herblore_level` + `agility_level` + `thieving_level` + `slayer_level` + `farming_level` + `runecraft_level` + `hunter_level` + `construction_level`) VIRTUAL,
`attack_rank` int(11) NOT NULL,
`defence_rank` int(11) NOT NULL,
`strength_rank` int(11) NOT NULL,
`hitpoints_rank` int(11) NOT NULL,
`ranged_rank` int(11) NOT NULL,
`prayer_rank` int(11) NOT NULL,
`magic_rank` int(11) NOT NULL,
`cooking_rank` int(11) NOT NULL,
`woodcutting_rank` int(11) NOT NULL,
`fletching_rank` int(11) NOT NULL,
`fishing_rank` int(11) NOT NULL,
`firemaking_rank` int(11) NOT NULL,
`crafting_rank` int(11) NOT NULL,
`smithing_rank` int(11) NOT NULL,
`mining_rank` int(11) NOT NULL,
`herblore_rank` int(11) NOT NULL,
`agility_rank` int(11) NOT NULL,
`thieving_rank` int(11) NOT NULL,
`slayer_rank` int(11) NOT NULL,
`farming_rank` int(11) NOT NULL,
`runecraft_rank` int(11) NOT NULL,
`hunter_rank` int(11) NOT NULL,
`construction_rank` int(11) NOT NULL,
`overall_rank` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `player_time` (`player`,`time`),
KEY `idx_time` (`time`),
CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2019-02-15 21:01:17

View File

@@ -0,0 +1,110 @@
{{#info}}
# {{title}}
{{join schemes " | "}}://{{host}}{{basePath}}
{{description}}
{{#contact}}
[**Contact the developer**](mailto:{{email}})
{{/contact}}
**Version** {{version}}
{{#if termsOfService}}
[**Terms of Service**]({{termsOfService}})
{{/if}}
{{/info}}
{{#if consumes}}__Consumes:__ {{join consumes ", "}}{{/if}}
{{#if produces}}__Produces:__ {{join produces ", "}}{{/if}}
{{#if securityDefinitions}}
# Security Definitions
{{> security}}
{{/if}}
<details>
<summary><b>Table Of Contents</b></summary>
[toc]
</details>
# APIs
{{#each paths}}
## {{@key}}
{{#this}}
{{#get}}
### GET
{{> operation}}
{{/get}}
{{#put}}
### PUT
{{> operation}}
{{/put}}
{{#post}}
### POST
{{> operation}}
{{/post}}
{{#delete}}
### DELETE
{{> operation}}
{{/delete}}
{{#option}}
### OPTION
{{> operation}}
{{/option}}
{{#patch}}
### PATCH
{{> operation}}
{{/patch}}
{{#head}}
### HEAD
{{> operation}}
{{/head}}
{{/this}}
{{/each}}
# Definitions
{{#each definitions}}
## <a name="/definitions/{{key}}">{{@key}}</a>
<table>
<tr>
<th>name</th>
<th>type</th>
<th>required</th>
<th>description</th>
<th>example</th>
</tr>
{{#each this.properties}}
<tr>
<td>{{@key}}</td>
<td>
{{#ifeq type "array"}}
{{#items.$ref}}
{{type}}[<a href="{{items.$ref}}">{{basename items.$ref}}</a>]
{{/items.$ref}}
{{^items.$ref}}{{type}}[{{items.type}}]{{/items.$ref}}
{{else}}
{{#$ref}}<a href="{{$ref}}">{{basename $ref}}</a>{{/$ref}}
{{^$ref}}{{type}}{{#format}} ({{format}}){{/format}}{{/$ref}}
{{/ifeq}}
</td>
<td>{{#required}}required{{/required}}{{^required}}optional{{/required}}</td>
<td>{{#description}}{{{description}}}{{/description}}{{^description}}-{{/description}}</td>
<td>{{example}}</td>
</tr>
{{/each}}
</table>
{{/each}}

View File

@@ -0,0 +1,71 @@
{{#deprecated}}-deprecated-{{/deprecated}}
<a id="{{operationId}}">{{summary}}</a>
{{description}}
{{#if externalDocs.url}}{{externalDocs.description}}. [See external documents for more details]({{externalDocs.url}})
{{/if}}
{{#if security}}
#### Security
{{/if}}
{{#security}}
{{#each this}}
* {{@key}}
{{#this}} * {{this}}
{{/this}}
{{/each}}
{{/security}}
#### Request
{{#if consumes}}__Content-Type:__ {{join consumes ", "}}{{/if}}
##### Parameters
{{#if parameters}}
<table>
<tr>
<th>Name</th>
<th>Located in</th>
<th>Required</th>
<th>Description</th>
<th>Default</th>
<th>Schema</th>
</tr>
{{/if}}
{{#parameters}}
<tr>
<th>{{name}}</th>
<td>{{in}}</td>
<td>{{#if required}}yes{{else}}no{{/if}}</td>
<td>{{description}}{{#if pattern}} (**Pattern**: `{{pattern}}`){{/if}}</td>
<td> - </td>
{{#ifeq in "body"}}
<td>
{{#ifeq schema.type "array"}}Array[<a href="{{schema.items.$ref}}">{{basename schema.items.$ref}}</a>]{{/ifeq}}
{{#schema.$ref}}<a href="{{schema.$ref}}">{{basename schema.$ref}}</a> {{/schema.$ref}}
</td>
{{else}}
{{#ifeq type "array"}}
<td>Array[{{items.type}}] ({{collectionFormat}})</td>
{{else}}
<td>{{type}} {{#format}}({{format}}){{/format}}</td>
{{/ifeq}}
{{/ifeq}}
</tr>
{{/parameters}}
{{#if parameters}}
</table>
{{/if}}
#### Response
{{#if produces}}__Content-Type:__ {{join produces ", "}}{{/if}}
| Status Code | Reason | Response Model |
|-------------|-------------|----------------|
{{#each responses}}| {{@key}} | {{description}} | {{#schema.$ref}}<a href="{{schema.$ref}}">{{basename schema.$ref}}</a>{{/schema.$ref}}{{#ifeq schema.type "array"}}Array[<a href="{{schema.items.$ref}}">{{basename schema.items.$ref}}</a>]{{/ifeq}}{{^schema}} - {{/schema}}|
{{/each}}

View File

@@ -0,0 +1,88 @@
{{#each securityDefinitions}}
### {{@key}}
{{#this}}
{{#ifeq type "oauth2"}}
<table>
<tr>
<th>type</th>
<th colspan="2">{{type}}</th>
</tr>
{{#if description}}
<tr>
<th>description</th>
<th colspan="2">{{description}}</th>
</tr>
{{/if}}
{{#if authorizationUrl}}
<tr>
<th>authorizationUrl</th>
<th colspan="2">{{authorizationUrl}}</th>
</tr>
{{/if}}
{{#if flow}}
<tr>
<th>flow</th>
<th colspan="2">{{flow}}</th>
</tr>
{{/if}}
{{#if tokenUrl}}
<tr>
<th>tokenUrl</th>
<th colspan="2">{{tokenUrl}}</th>
</tr>
{{/if}}
{{#if scopes}}
<tr>
<td rowspan="3">scopes</td>
{{#each scopes}}
<td>{{@key}}</td>
<td>{{this}}</td>
</tr>
<tr>
{{/each}}
</tr>
{{/if}}
</table>
{{/ifeq}}
{{#ifeq type "apiKey"}}
<table>
<tr>
<th>type</th>
<th colspan="2">{{type}}</th>
</tr>
{{#if description}}
<tr>
<th>description</th>
<th colspan="2">{{description}}</th>
</tr>
{{/if}}
{{#if name}}
<tr>
<th>name</th>
<th colspan="2">{{name}}</th>
</tr>
{{/if}}
{{#if in}}
<tr>
<th>in</th>
<th colspan="2">{{in}}</th>
</tr>
{{/if}}
</table>
{{/ifeq}}
{{#ifeq type "basic"}}
<table>
<tr>
<th>type</th>
<th colspan="2">{{type}}</th>
</tr>
{{#if description}}
<tr>
<th>description</th>
<th colspan="2">{{description}}</th>
</tr>
{{/if}}
</table>
{{/ifeq}}
{{/this}}
{{/each}}

View File

@@ -0,0 +1,49 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"/>
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
.markdown-body table {
display: table;
}
</style>
<!-- JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.0/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/JanLoebel/showdown-toc/src/showdown-toc.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
var markdown = document.querySelector('noscript').innerText
var converter = new showdown.Converter({emoji: true, extensions: ['toc']})
converter.setFlavor('github')
var html = converter.makeHtml(markdown)
document.body.innerHTML = html
})
</script>
<title>{{info.title}} {{info.version}}</title>
</head>
<body class="markdown-body">
<noscript>{{>markdown}}</noscript>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!--
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.
-->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>RuneLite API</display-name>
</web-app>

View File

@@ -0,0 +1,17 @@
# Use in-memory database for tests
datasource:
runelite:
jndiName:
driverClassName: org.h2.Driver
type: org.h2.jdbcx.JdbcDataSource
url: jdbc:h2:mem:runelite
runelite-cache:
jndiName:
driverClassName: org.h2.Driver
type: org.h2.jdbcx.JdbcDataSource
url: jdbc:h2:mem:cache
runelite-tracker:
jndiName:
driverClassName: org.h2.Driver
type: org.h2.jdbcx.JdbcDataSource
url: jdbc:h2:mem:xptracker