Merge remote-tracking branch 'upstream/master' into runelite

Also pulled out a couple of our merged apis into our own classes. Getting much easier to keep up to date with their data

# Conflicts:
#	.github/FUNDING.yml
#	.github/workflows/CI.yml
#	cache-client/pom.xml
#	cache-updater/pom.xml
#	cache/pom.xml
#	cache/src/main/java/net/runelite/cache/fs/jagex/DiskStorage.java
#	ci/build.sh
#	http-api/pom.xml
#	http-service/pom.xml
#	http-service/src/main/java/net/runelite/http/service/chat/ChatController.java
#	http-service/src/main/java/net/runelite/http/service/config/ConfigController.java
#	http-service/src/main/java/net/runelite/http/service/config/ConfigService.java
#	http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java
#	http-service/src/main/java/net/runelite/http/service/ge/Trade.java
#	http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java
#	http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java
#	pom.xml
#	runelite-api/pom.xml
#	runelite-api/src/main/java/net/runelite/api/Client.java
#	runelite-api/src/main/java/net/runelite/api/ObjectID.java
#	runelite-api/src/main/java/net/runelite/api/ParamHolder.java
#	runelite-api/src/main/java/net/runelite/api/ParamID.java
#	runelite-api/src/main/java/net/runelite/api/Preferences.java
#	runelite-api/src/main/java/net/runelite/api/ScriptEvent.java
#	runelite-api/src/main/java/net/runelite/api/ScriptID.java
#	runelite-api/src/main/java/net/runelite/api/SettingID.java
#	runelite-api/src/main/java/net/runelite/api/StructComposition.java
#	runelite-api/src/main/java/net/runelite/api/StructID.java
#	runelite-api/src/main/java/net/runelite/api/Varbits.java
#	runelite-api/src/main/java/net/runelite/api/events/PlayerChanged.java
#	runelite-api/src/main/java/net/runelite/api/events/PostStructComposition.java
#	runelite-api/src/main/java/net/runelite/api/events/WidgetClosed.java
#	runelite-api/src/main/java/net/runelite/api/events/WidgetHiddenChanged.java
#	runelite-api/src/main/java/net/runelite/api/events/WidgetPositioned.java
#	runelite-api/src/main/java/net/runelite/api/events/WorldChanged.java
#	runelite-api/src/main/java/net/runelite/api/widgets/Widget.java
#	runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
#	runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java
#	runelite-api/src/main/java/net/runelite/api/widgets/WidgetModalMode.java
#	runelite-api/src/test/java/net/runelite/api/plugins/combatlevel/CombatLevelOverlayTest.java
#	runelite-client/pom.xml
#	runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java
#	runelite-client/src/main/java/net/runelite/client/callback/Hooks.java
#	runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java
#	runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java
#	runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java
#	runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoomTimer.java
#	runelite-client/src/main/java/net/runelite/client/plugins/tearsofguthix/TearsOfGuthixOverlay.java
#	runelite-client/src/main/java/net/runelite/client/plugins/tearsofguthix/TearsOfGuthixPlugin.java
#	runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableHeader.java
#	runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java
#	runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
#	runelite-client/src/main/resources/net/runelite/client/runelite.properties
#	runelite-client/src/main/scripts/OptionsPanelZoomUpdater.hash
#	runelite-client/src/test/java/net/runelite/client/config/ConfigManagerTest.java
#	runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java
#	runelite-client/src/test/java/net/runelite/client/plugins/chatfilter/ChatFilterPluginTest.java
#	runelite-client/src/test/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPluginTest.java
#	runelite-client/src/test/java/net/runelite/client/plugins/discord/DiscordStateTest.java
#	runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangePluginTest.java
#	runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java
#	runelite-client/src/test/java/net/runelite/client/plugins/specialcounter/SpecialCounterPluginTest.java
#	runelite-client/src/test/java/net/runelite/client/plugins/timers/TimersPluginTest.java
#	runelite-client/src/test/java/net/runelite/client/util/ColorUtilTest.java
#	runelite-script-assembler-plugin/pom.xml
This commit is contained in:
TheRealNull
2021-01-14 14:41:17 -05:00
99 changed files with 7485 additions and 1754 deletions

View File

@@ -0,0 +1,250 @@
/*
* 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.Duels;
import net.runelite.http.api.chat.LayoutRoom;
import net.runelite.http.api.chat.Task;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/chat")
public class ChatController
{
private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]");
private static final int STRING_MAX_LENGTH = 50;
private static final int MAX_LAYOUT_ROOMS = 16;
private 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 gc = chatService.getGc(name);
if (gc == null)
{
throw new NotFoundException();
}
return gc;
}
@PostMapping("/task")
public void submitTask(@RequestParam String name, @RequestParam("task") String taskName, @RequestParam int amount,
@RequestParam int initialAmount, @RequestParam String location)
{
Matcher mTask = STRING_VALIDATION.matcher(taskName);
Matcher mLocation = STRING_VALIDATION.matcher(location);
if (mTask.find() || taskName.length() > STRING_MAX_LENGTH ||
mLocation.find() || location.length() > STRING_MAX_LENGTH)
{
return;
}
Task task = new Task();
task.setTask(taskName);
task.setAmount(amount);
task.setInitialAmount(initialAmount);
task.setLocation(location);
chatService.setTask(name, task);
}
@GetMapping("/task")
public ResponseEntity<Task> getTask(@RequestParam String name)
{
Task task = chatService.getTask(name);
if (task == null)
{
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.build();
}
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(2, TimeUnit.MINUTES).cachePublic())
.body(task);
}
@PostMapping("/pb")
public void submitPb(@RequestParam String name, @RequestParam String boss, @RequestParam 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;
}
@PostMapping("/duels")
public void submitDuels(@RequestParam String name, @RequestParam int wins,
@RequestParam int losses,
@RequestParam int winningStreak, @RequestParam int losingStreak)
{
if (wins < 0 || losses < 0 || winningStreak < 0 || losingStreak < 0)
{
return;
}
Duels duels = new Duels();
duels.setWins(wins);
duels.setLosses(losses);
duels.setWinningStreak(winningStreak);
duels.setLosingStreak(losingStreak);
chatService.setDuels(name, duels);
}
@GetMapping("/duels")
public Duels getDuels(@RequestParam String name)
{
Duels duels = chatService.getDuels(name);
if (duels == null)
{
throw new NotFoundException();
}
return duels;
}
@PostMapping("/layout")
public void submitLayout(@RequestParam String name, @RequestBody LayoutRoom[] rooms)
{
if (rooms.length > MAX_LAYOUT_ROOMS)
{
return;
}
chatService.setLayout(name, rooms);
}
@GetMapping("/layout")
public LayoutRoom[] getLayout(@RequestParam String name)
{
LayoutRoom[] layout = chatService.getLayout(name);
if (layout == null)
{
throw new NotFoundException();
}
return layout;
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.config;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.runelite.http.api.config.Configuration;
import net.runelite.http.service.account.AuthFilter;
import net.runelite.http.service.account.beans.SessionEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/config")
public class ConfigController
{
private final ConfigService configService;
private final AuthFilter authFilter;
@Autowired
public ConfigController(ConfigService configService, AuthFilter authFilter)
{
this.configService = configService;
this.authFilter = authFilter;
}
@GetMapping
public Configuration get(HttpServletRequest request, HttpServletResponse response) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return null;
}
return configService.get(session.getUser());
}
@PatchMapping
public List<String> patch(
HttpServletRequest request,
HttpServletResponse response,
@RequestBody Configuration changes
) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return null;
}
List<String> failures = configService.patch(session.getUser(), changes);
if (failures.size() != 0)
{
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return failures;
}
return null;
}
@RequestMapping(path = "/{key:.+}", method = PUT)
public void setKey(
HttpServletRequest request,
HttpServletResponse response,
@PathVariable String key,
@RequestBody(required = false) String value
) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return;
}
if (!configService.setKey(session.getUser(), key, value))
{
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
@RequestMapping(path = "/{key:.+}", method = DELETE)
public void unsetKey(
HttpServletRequest request,
HttpServletResponse response,
@PathVariable String key
) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return;
}
if (!configService.unsetKey(session.getUser(), key))
{
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}

View File

@@ -0,0 +1,341 @@
/*
* Copyright (c) 2017-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.config;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import static com.mongodb.client.model.Filters.eq;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.client.model.UpdateOptions;
import static com.mongodb.client.model.Updates.combine;
import static com.mongodb.client.model.Updates.set;
import static com.mongodb.client.model.Updates.unset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.config.ConfigEntry;
import net.runelite.http.api.config.Configuration;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class ConfigService
{
private static final int MAX_DEPTH = 8;
private static final int MAX_VALUE_LENGTH = 262144;
private final Gson GSON = RuneLiteAPI.GSON;
private final UpdateOptions upsertUpdateOptions = new UpdateOptions().upsert(true);
private final MongoCollection<Document> mongoCollection;
@Autowired
public ConfigService(
MongoClient mongoClient,
@Value("${mongo.database}") String databaseName
)
{
MongoDatabase database = mongoClient.getDatabase(databaseName);
MongoCollection<Document> collection = database.getCollection("config");
this.mongoCollection = collection;
// Create unique index on _userId
IndexOptions indexOptions = new IndexOptions().unique(true);
collection.createIndex(Indexes.ascending("_userId"), indexOptions);
}
private Document getConfig(int userId)
{
return mongoCollection.find(eq("_userId", userId)).first();
}
public Configuration get(int userId)
{
Map<String, Object> configMap = getConfig(userId);
if (configMap == null || configMap.isEmpty())
{
return new Configuration(Collections.emptyList());
}
List<ConfigEntry> config = new ArrayList<>();
for (String group : configMap.keySet())
{
// Reserved keys
if (group.startsWith("_") || group.startsWith("$"))
{
continue;
}
Map<String, Object> groupMap = (Map) configMap.get(group);
for (Map.Entry<String, Object> entry : groupMap.entrySet())
{
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Map || value instanceof Collection)
{
value = GSON.toJson(entry.getValue());
}
else if (value == null)
{
continue;
}
ConfigEntry configEntry = new ConfigEntry();
configEntry.setKey(group + "." + key.replace(':', '.'));
configEntry.setValue(value.toString());
config.add(configEntry);
}
}
return new Configuration(config);
}
public List<String> patch(int userID, Configuration config)
{
List<String> failures = new ArrayList<>();
List<Bson> sets = new ArrayList<>(config.getConfig().size());
for (ConfigEntry entry : config.getConfig())
{
Bson s = setForKV(entry.getKey(), entry.getValue());
if (s == null)
{
failures.add(entry.getKey());
}
else
{
sets.add(s);
}
}
if (sets.size() > 0)
{
mongoCollection.updateOne(
eq("_userId", userID),
combine(sets),
upsertUpdateOptions
);
}
return failures;
}
@Nullable
private Bson setForKV(String key, @Nullable String value)
{
if (key.startsWith("$") || key.startsWith("_"))
{
return null;
}
String[] split = key.split("\\.", 2);
if (split.length != 2)
{
return null;
}
String dbKey = split[0] + "." + split[1].replace('.', ':');
if (Strings.isNullOrEmpty(value))
{
return unset(dbKey);
}
if (!validateJson(value))
{
return null;
}
Object jsonValue = parseJsonString(value);
return set(dbKey, jsonValue);
}
public boolean setKey(
int userId,
String key,
@Nullable String value
)
{
Bson set = setForKV(key, value);
if (set == null)
{
return false;
}
mongoCollection.updateOne(eq("_userId", userId),
set,
upsertUpdateOptions);
return true;
}
public boolean unsetKey(
int userId,
String key
)
{
Bson set = setForKV(key, null);
if (set == null)
{
return false;
}
mongoCollection.updateOne(eq("_userId", userId), set);
return true;
}
@VisibleForTesting
static Object parseJsonString(String value)
{
Object jsonValue;
try
{
jsonValue = RuneLiteAPI.GSON.fromJson(value, Object.class);
if (jsonValue == null)
{
return value;
}
else if (jsonValue instanceof Double || jsonValue instanceof Float)
{
Number number = (Number) jsonValue;
if (Math.floor(number.doubleValue()) == number.doubleValue() && !Double.isInfinite(number.doubleValue()))
{
// value is an int or long. 'number' might be truncated so parse it from 'value'
try
{
jsonValue = Integer.parseInt(value);
}
catch (NumberFormatException ex)
{
try
{
jsonValue = Long.parseLong(value);
}
catch (NumberFormatException ex2)
{
}
}
}
}
}
catch (JsonSyntaxException ex)
{
jsonValue = value;
}
return jsonValue;
}
@VisibleForTesting
static boolean validateJson(String value)
{
try
{
// I couldn't figure out a better way to do this than a second json parse
JsonElement jsonElement = RuneLiteAPI.GSON.fromJson(value, JsonElement.class);
if (jsonElement == null)
{
return value.length() < MAX_VALUE_LENGTH;
}
return validateObject(jsonElement, 1);
}
catch (JsonSyntaxException ex)
{
// the client submits the string representation of objects which is not always valid json,
// eg. a value with a ':' in it. We just ignore it now. We can't json encode the values client
// side due to them already being strings, which prevents gson from being able to convert them
// to ints/floats/maps etc.
return value.length() < MAX_VALUE_LENGTH;
}
}
private static boolean validateObject(JsonElement jsonElement, int depth)
{
if (depth >= MAX_DEPTH)
{
return false;
}
if (jsonElement.isJsonObject())
{
JsonObject jsonObject = jsonElement.getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet())
{
JsonElement element = entry.getValue();
if (!validateObject(element, depth + 1))
{
return false;
}
}
}
else if (jsonElement.isJsonArray())
{
JsonArray jsonArray = jsonElement.getAsJsonArray();
for (int i = 0; i < jsonArray.size(); ++i)
{
JsonElement element = jsonArray.get(i);
if (!validateObject(element, depth + 1))
{
return false;
}
}
}
else if (jsonElement.isJsonPrimitive())
{
JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive();
String value = jsonPrimitive.getAsString();
if (value.length() >= MAX_VALUE_LENGTH)
{
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,158 @@
/*
* 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 com.google.gson.Gson;
import java.io.IOException;
import java.time.Instant;
import java.util.Collection;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.ge.GrandExchangeTrade;
import net.runelite.http.service.account.AuthFilter;
import net.runelite.http.service.account.beans.SessionEntry;
import net.runelite.http.service.util.redis.RedisPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
@RestController
@RequestMapping("/ge")
public class GrandExchangeController
{
private static final Gson GSON = RuneLiteAPI.GSON;
private final GrandExchangeService grandExchangeService;
private final AuthFilter authFilter;
private final RedisPool redisPool;
@Autowired
public GrandExchangeController(GrandExchangeService grandExchangeService, AuthFilter authFilter, RedisPool redisPool)
{
this.grandExchangeService = grandExchangeService;
this.authFilter = authFilter;
this.redisPool = redisPool;
}
@PostMapping
public void submit(HttpServletRequest request, HttpServletResponse response, @RequestBody GrandExchangeTrade grandExchangeTrade) throws IOException
{
SessionEntry session = null;
if (request.getHeader(RuneLiteAPI.RUNELITE_AUTH) != null)
{
session = authFilter.handle(request, response);
if (session == null)
{
// error is set here on the response, so we shouldn't continue
return;
}
}
Integer userId = session == null ? null : session.getUser();
// We don't keep track of pending trades in the web UI, so only add cancelled or completed trades
if (userId != null &&
grandExchangeTrade.getQty() > 0 &&
(grandExchangeTrade.isCancel() || grandExchangeTrade.getQty() == grandExchangeTrade.getTotal()))
{
grandExchangeService.add(userId, grandExchangeTrade);
}
Trade trade = new Trade();
trade.setBuy(grandExchangeTrade.isBuy());
trade.setCancel(grandExchangeTrade.isCancel());
trade.setLogin(grandExchangeTrade.isLogin());
trade.setItemId(grandExchangeTrade.getItemId());
trade.setQty(grandExchangeTrade.getQty());
trade.setDqty(grandExchangeTrade.getDqty());
trade.setTotal(grandExchangeTrade.getTotal());
trade.setSpent(grandExchangeTrade.getDspent());
trade.setOffer(grandExchangeTrade.getOffer());
trade.setSlot(grandExchangeTrade.getSlot());
trade.setTime((int) (System.currentTimeMillis() / 1000L));
trade.setMachineId(request.getHeader(RuneLiteAPI.RUNELITE_MACHINEID));
trade.setUserId(userId);
trade.setIp(request.getHeader("X-Forwarded-For"));
trade.setUa(request.getHeader("User-Agent"));
trade.setWorldType(grandExchangeTrade.getWorldType());
trade.setSeq(grandExchangeTrade.getSeq());
Instant resetTime = grandExchangeTrade.getResetTime();
trade.setResetTime(resetTime == null ? 0L : resetTime.getEpochSecond());
String json = GSON.toJson(trade);
try (Jedis jedis = redisPool.getResource())
{
jedis.publish("ge", json);
}
}
@GetMapping
public Collection<GrandExchangeTradeHistory> get(HttpServletRequest request, HttpServletResponse response,
@RequestParam(required = false, defaultValue = "1024") int limit,
@RequestParam(required = false, defaultValue = "0") int offset) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return null;
}
return grandExchangeService.get(session.getUser(), limit, offset).stream()
.map(GrandExchangeController::convert)
.collect(Collectors.toList());
}
private static GrandExchangeTradeHistory convert(TradeEntry tradeEntry)
{
GrandExchangeTradeHistory grandExchangeTrade = new GrandExchangeTradeHistory();
grandExchangeTrade.setBuy(tradeEntry.getAction() == TradeAction.BUY);
grandExchangeTrade.setItemId(tradeEntry.getItem());
grandExchangeTrade.setQuantity(tradeEntry.getQuantity());
grandExchangeTrade.setPrice(tradeEntry.getPrice());
grandExchangeTrade.setTime(tradeEntry.getTime());
return grandExchangeTrade;
}
@DeleteMapping
public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException
{
SessionEntry session = authFilter.handle(request, response);
if (session == null)
{
return;
}
grandExchangeService.delete(session.getUser());
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.config;
import com.google.common.collect.ImmutableMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class ConfigServiceTest
{
@Test
public void testParseJsonString()
{
assertEquals(1, ConfigService.parseJsonString("1"));
assertEquals(3.14, ConfigService.parseJsonString("3.14"));
assertEquals(1L << 32, ConfigService.parseJsonString("4294967296"));
assertEquals("test", ConfigService.parseJsonString("test"));
assertEquals("test", ConfigService.parseJsonString("\"test\""));
assertEquals(ImmutableMap.of("key", "value"), ConfigService.parseJsonString("{\"key\": \"value\"}"));
}
@Test
public void testValidateJson()
{
assertTrue(ConfigService.validateJson("1"));
assertTrue(ConfigService.validateJson("3.14"));
assertTrue(ConfigService.validateJson("test"));
assertTrue(ConfigService.validateJson("\"test\""));
assertTrue(ConfigService.validateJson("key:value"));
assertTrue(ConfigService.validateJson("{\"key\": \"value\"}"));
assertTrue(ConfigService.validateJson("\n"));
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.hiscore;
import java.io.IOException;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import net.runelite.http.api.hiscore.HiscoreResult;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class HiscoreServiceTest
{
private static final String RESPONSE = "654683,705,1304518\n"
+ "679419,50,107181\n"
+ "550667,48,85764\n"
+ "861497,50,101366\n"
+ "891591,48,87843\n"
+ "-1,1,4\n"
+ "840255,27,10073\n"
+ "1371912,10,1310\n"
+ "432193,56,199795\n"
+ "495638,56,198304\n"
+ "514466,37,27502\n"
+ "456981,54,159727\n"
+ "459159,49,93010\n"
+ "1028855,8,823\n"
+ "862906,29,12749\n"
+ "795020,31,16097\n"
+ "673591,5,495\n"
+ "352676,51,112259\n"
+ "428419,40,37235\n"
+ "461887,43,51971\n"
+ "598582,1,10\n"
+ "638177,1,0\n"
+ "516239,9,1000\n"
+ "492790,1,0\n"
+ "2,2460\n" // leagues
+ "-1,-1\n"
+ "73,1738\n"
+ "531,1432\n"
+ "324,212\n"
+ "8008,131\n"
+ "1337,911\n"
+ "42,14113\n"
+ "1,777\n"
+ "254,92\n"
+ "-1,-1\n" // lms
+ "1,241\n" // soul wars
+ "24870,37\n"
+ "15020,388\n"
+ "50463,147\n"
+ "-1,-1\n"
+ "92357,1\n"
+ "22758,637\n"
+ "22744,107\n"
+ "-1,-1\n"
+ "20150,17\n"
+ "29400,18\n"
+ "13465,172\n"
+ "1889,581\n"
+ "42891,11\n"
+ "1624,1957\n"
+ "1243,2465\n"
+ "1548,2020\n"
+ "-1,-1\n"
+ "16781,327\n"
+ "19004,149\n"
+ "-1,-1\n"
+ "72046,5\n"
+ "5158,374\n"
+ "20902,279\n"
+ "702,6495\n"
+ "10170,184\n"
+ "8064,202\n"
+ "6936,2\n"
+ "2335,9\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "19779,22\n"
+ "58283,10\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "29347,130\n"
+ "723,4\n"
+ "1264,38\n"
+ "44595,4\n"
+ "24820,4\n"
+ "12116,782\n"
+ "2299,724\n"
+ "19301,62\n"
+ "1498,5847\n";
private final MockWebServer server = new MockWebServer();
@Before
public void before() throws IOException
{
server.enqueue(new MockResponse().setBody(RESPONSE));
server.start();
}
@After
public void after() throws IOException
{
server.shutdown();
}
@Test
public void testNormalLookup() throws Exception
{
HiscoreTestService hiscores = new HiscoreTestService(server.url("/"));
HiscoreResult result = hiscores.lookupUsername("zezima", HiscoreEndpoint.NORMAL.getHiscoreURL());
Assert.assertEquals(50, result.getAttack().getLevel());
Assert.assertEquals(159727L, result.getFishing().getExperience());
Assert.assertEquals(492790, result.getConstruction().getRank());
Assert.assertEquals(1432, result.getClueScrollAll().getLevel());
Assert.assertEquals(324, result.getClueScrollBeginner().getRank());
Assert.assertEquals(8008, result.getClueScrollEasy().getRank());
Assert.assertEquals(911, result.getClueScrollMedium().getLevel());
Assert.assertEquals(42, result.getClueScrollHard().getRank());
Assert.assertEquals(777, result.getClueScrollElite().getLevel());
Assert.assertEquals(254, result.getClueScrollMaster().getRank());
Assert.assertEquals(-1, result.getLastManStanding().getLevel());
Assert.assertEquals(241, result.getSoulWarsZeal().getLevel());
Assert.assertEquals(2460, result.getLeaguePoints().getLevel());
Assert.assertEquals(37, result.getAbyssalSire().getLevel());
Assert.assertEquals(92357, result.getCallisto().getRank());
Assert.assertEquals(5847, result.getZulrah().getLevel());
}
}