Skip to content

Commit

Permalink
Release 1.8.0 (#45)
Browse files Browse the repository at this point in the history
* Expand docs
* New V2 resource to also show reward chest loot statistics.
  • Loading branch information
benjaminkomen authored Feb 2, 2020
1 parent d48386a commit 916fdc7
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 109 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Navigate to https://tibiawiki.dev to view the Swagger API of this project.
## Run locally
Clone this git project to your local computer and compile it using: `mvn clean install` from your favourite command line
terminal. Then execute: `mvn spring-boot:run` and open your browser on http://localhost:8080

Note that you need to add the [sample settings.xml](.travis.settings.xml) to your $HOME/.m2/settings.xml directory
with a valid username and github token with read packages scope.

You can now access the REST resources using your browser or any REST client such as Postman or curl from your command line.
E.g. navigating to http://localhost:8080/api/corpses should give you a list of corpses.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.tibiawiki</groupId>
<artifactId>TibiaWikiApi</artifactId>
<version>1.7.2</version>
<version>1.8.0</version>
<packaging>jar</packaging>
<name>TibiaWikiApi</name>
<url>https://github.com/benjaminkomen/TibiaWikiApi</url> <!-- https://tibiawiki.dev -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package com.tibiawiki.serviceinterface;

import benjaminkomen.jwiki.core.NS;
import com.tibiawiki.domain.enums.InfoboxTemplate;
import com.tibiawiki.domain.repositories.ArticleRepository;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.tibiawiki.process.RetrieveAny.CATEGORY_LISTS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.doReturn;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class LootStatisticsV2ResourceIT {

private static final NS LOOT_NAMESPACE = new NS(112);
@Autowired
private TestRestTemplate restTemplate;

@MockBean
private ArticleRepository articleRepository; // don't instantiate this real class, but use a mock implementation

private static final String LOOT_AMAZON_TEXT = "{{Loot2\n" +
"|version=8.6\n" +
"|kills=22009\n" +
"|name=Amazon\n" +
"|Empty, times:253\n" +
"|Dagger, times:17626, amount:1, total:17626\n" +
"|Skull, times:17604, amount:1-2, total:26348\n" +
"|Gold Coin, times:8829, amount:1-20, total:93176\n" +
"|Brown Bread, times:6496, amount:1, total:6496\n" +
"|Sabre, times:5098, amount:1, total:5098\n" +
"|Girlish Hair Decoration, times:2179, amount:1, total:2179\n" +
"|Protective Charm, times:1154, amount:1, total:1154\n" +
"|Torch, times:223, amount:1, total:223\n" +
"|Crystal Necklace, times:56, amount:1, total:56\n" +
"|Small Ruby, times:27, amount:1, total:27\n" +
"}}\n";

private static final String LOOT_FERUMBRAS_TEXT = "__NOWYSIWYG__\n\n" +
"{{Loot2\n" +
"|version=8.6\n" +
"|kills=49\n" +
"|name=Ferumbras\n" +
"|Ferumbras' Hat, times:49, total:3\n" +
"|Gold Coin, times:48, amount:18-184, total:4751\n" +
"|Gold Ingot, times:37, amount:1-2, total:52\n" +
"|Great Shield, times:13, amount:1, total:13\n" +
"|Spellbook of Lost Souls, times:13, amount:1, total:13\n" +
"|Golden Armor, times:12, amount:1, total:12\n" +
"}}\n" +
"\n" +
"{{Loot2_RC\n" +
"|version=8.6\n" +
"|kills=1\n" +
"|name=Ferumbras\n" +
"|Blue Gem, times:1, amount:1, total:1\n" +
"|Giant Shimmering Pearl, times:1, amount:1, total:1\n" +
"|Gold Coin, times:1, amount:100, total:100\n" +
"|Golden Armor, times:1, amount:1, total:1\n" +
"|Lightning Legs, times:1\n" +
"|Runed Sword, times:1, amount:1, total:1\n" +
"|Small Emerald, times:1, amount:10, total:10\n" +
"}}\n" +
"\n" +
"{{Loot\n" +
"|version=8.54\n" +
"|kills=4\n" +
"|name=Ferumbras\n" +
"|[[Gold Coin]], 399\n" +
"|[[Small Ruby]], 126\n" +
"|[[Small Diamond]], 45\n" +
"|[[Gold Ingot]], 6\n" +
"|[[Ferumbras' Hat]], 4\n" +
"|[[Small Topaz]], 3\n" +
"|[[Spellbook of Lost Souls]], 3\n" +
"}}\n" +
"<br/>Average gold: 99.75";

@Test
void givenGetLootsNotExpanded_whenCorrectRequest_thenResponseIsOkAndContainsTwoLootNames() {
doReturn(Arrays.asList("foo", "bar")).when(articleRepository).getPageNamesFromCategory(InfoboxTemplate.LOOT.getCategoryName(), LOOT_NAMESPACE);

final ResponseEntity<List> result = restTemplate.getForEntity("/api/v2/loot?expand=false", List.class);

assertThat(result.getStatusCode(), is(HttpStatus.OK));
assertThat(result.getBody().size(), is(2));
assertThat(result.getBody().get(0), is("foo"));
assertThat(result.getBody().get(1), is("bar"));
}

@Test
void givenGetLootsExpanded_whenCorrectRequest_thenResponseIsOkAndContainsOneLoot() {
doReturn(Collections.emptyList()).when(articleRepository).getPageNamesFromCategory(CATEGORY_LISTS);
doReturn(Collections.singletonList("Loot:Amazon")).when(articleRepository).getPageNamesFromCategory(InfoboxTemplate.LOOT.getCategoryName(), LOOT_NAMESPACE);
doReturn(Map.of("Loot:Amazon", LOOT_AMAZON_TEXT)).when(articleRepository).getArticlesFromCategory(Collections.singletonList("Loot:Amazon"));

final ResponseEntity<List> result = restTemplate.getForEntity("/api/v2/loot?expand=true", List.class);

assertThat(result.getStatusCode(), is(HttpStatus.OK));
assertThat(result.getBody().size(), is(1));

var loot2 = ((Map) ((Map) result.getBody().get(0)).get("loot2"));

assertThat(loot2.get("kills"), is("22009"));
assertThat(loot2.get("name"), is("Amazon"));
assertThat(loot2.get("version"), is("8.6"));
assertThat(loot2.get("pageName"), is("Loot:Amazon"));
}

@Test
void givenGetLootsByName_whenCorrectRequest_thenResponseIsOkAndContainsTheLoot() {
doReturn(LOOT_AMAZON_TEXT).when(articleRepository).getArticle("Loot_Statistics:Amazon");

final ResponseEntity<String> result = restTemplate.getForEntity("/api/v2/loot/Amazon", String.class);
assertThat(result.getStatusCode(), is(HttpStatus.OK));

final JSONObject resultAsJSON = new JSONObject(result.getBody()).getJSONObject("loot2");
assertThat(resultAsJSON.get("kills"), is("22009"));
assertThat(resultAsJSON.get("name"), is("Amazon"));
assertThat(resultAsJSON.get("version"), is("8.6"));
assertThat(resultAsJSON.get("pageName"), is("Loot_Statistics:Amazon"));
}

@Test
void givenGetLootsByName_whenCorrectRequest_thenResponseIsOkAndContainsTwoLootEntities() {
doReturn(LOOT_FERUMBRAS_TEXT).when(articleRepository).getArticle("Loot_Statistics:Ferumbras");

final ResponseEntity<String> result = restTemplate.getForEntity("/api/v2/loot/Ferumbras", String.class);
assertThat(result.getStatusCode(), is(HttpStatus.OK));

final JSONObject loot2Result = new JSONObject(result.getBody()).getJSONObject("loot2");
assertThat(loot2Result.get("kills"), is("49"));
assertThat(loot2Result.get("name"), is("Ferumbras"));
assertThat(loot2Result.get("version"), is("8.6"));
assertThat(loot2Result.get("pageName"), is("Loot_Statistics:Ferumbras"));

final JSONObject loot2RewardChestResult = new JSONObject(result.getBody()).getJSONObject("loot2_rc");
assertThat(loot2RewardChestResult.get("kills"), is("1"));
assertThat(loot2RewardChestResult.get("name"), is("Ferumbras"));
assertThat(loot2RewardChestResult.get("version"), is("8.6"));
assertThat(loot2RewardChestResult.get("pageName"), is("Loot_Statistics:Ferumbras"));
}

@Test
void givenGetLootsByName_whenWrongRequest_thenResponseIsNotFound() {
doReturn(null).when(articleRepository).getArticle("Loot:Foobar");

final ResponseEntity<String> result = restTemplate.getForEntity("/api/v2/loot/Foobar", String.class);
assertThat(result.getStatusCode(), is(HttpStatus.NOT_FOUND));
}
}
57 changes: 48 additions & 9 deletions src/main/java/com/tibiawiki/domain/factories/ArticleFactory.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.tibiawiki.domain.factories;

import com.tibiawiki.domain.utils.TemplateUtils;
import io.vavr.Tuple2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;

/**
* Conversion from Article to infoboxPartOfArticle.
Expand All @@ -17,6 +20,9 @@ public class ArticleFactory {
private static final Logger log = LoggerFactory.getLogger(ArticleFactory.class);
private static final String INFOBOX_HEADER = "{{Infobox";
private static final String LOOT2_HEADER = "{{Loot2";
private static final Pattern LOOT2_HEADER_PATTERN = Pattern.compile("\\{\\{Loot2\\n");
private static final String LOOT2_RC_HEADER = "{{Loot2_RC";
private static final Pattern LOOT2_RC_HEADER_REGEX = Pattern.compile("\\{\\{Loot2_RC\\n");

public String extractInfoboxPartOfArticle(String articleContent) {
return extractInfoboxPartOfArticle(Map.entry("Unknown", articleContent));
Expand All @@ -38,13 +44,18 @@ public String extractInfoboxPartOfArticle(Map.Entry<String, String> pageNameAndA
return "";
}

return TemplateUtils.getBetweenOuterBalancedBrackets(articleContent, INFOBOX_HEADER);
return TemplateUtils.getBetweenOuterBalancedBrackets(articleContent, INFOBOX_HEADER)
.orElse("");
}

public String extractLootPartOfArticle(String pageName, String articleContent) {
return extractLootPartOfArticle(Map.entry(pageName, articleContent));
}

public Map<String, String> extractAllLootPartsOfArticle(String pageName, String articleContent) {
return extractAllLootPartsOfArticle(Map.entry(pageName, articleContent));
}

/**
* Given a certain Article, extract the part from it which is the first loot statistics template, or an empty String if it does not contain
* an Loot2 template (which is perfectly valid in some cases).
Expand All @@ -53,26 +64,54 @@ public String extractLootPartOfArticle(Map.Entry<String, String> pageNameAndArti
final String pageName = pageNameAndArticleContent.getKey();
final String articleContent = pageNameAndArticleContent.getValue();

if (!articleContent.contains(LOOT2_HEADER)) {
if (!LOOT2_HEADER_PATTERN.matcher(articleContent).find()) {
if (log.isWarnEnabled()) {
log.warn("Cannot extract loot statistics template from article '{}'," +
" since it contains no Loot2 template.", pageName);
}
return "";
}

return TemplateUtils.getBetweenOuterBalancedBrackets(articleContent, LOOT2_HEADER);
return TemplateUtils.getBetweenOuterBalancedBrackets(articleContent, LOOT2_HEADER)
.orElse("");
}

/**
* Given a certain Article, extract the parts of all different supported loot statistics templates (Loot2 or Loot2_RC).
*/
public Map<String, String> extractAllLootPartsOfArticle(Map.Entry<String, String> pageNameAndArticleContent) {
final String pageName = pageNameAndArticleContent.getKey();
final String articleContent = pageNameAndArticleContent.getValue();

if (!LOOT2_HEADER_PATTERN.matcher(articleContent).find() && !LOOT2_RC_HEADER_REGEX.matcher(articleContent).find()) {
if (log.isWarnEnabled()) {
log.warn("Cannot extract loot statistics template from article '{}'," +
" since it contains no Loot2 or Loot2_RC template.", pageName);
}
return Collections.emptyMap();
}

var result = new HashMap<String, String>(2);
var loot2 = LOOT2_HEADER_PATTERN.matcher(articleContent).find()
? TemplateUtils.getBetweenOuterBalancedBrackets(articleContent, LOOT2_HEADER)
: Optional.empty();
var loot2Rc = LOOT2_RC_HEADER_REGEX.matcher(articleContent).find()
? TemplateUtils.getBetweenOuterBalancedBrackets(articleContent, LOOT2_RC_HEADER)
: Optional.empty();

loot2.ifPresent(s -> result.put("loot2", (String) s));
loot2Rc.ifPresent(s -> result.put("loot2_rc", (String) s));

return result;
}

/**
* @param originalArticleContent the original article content with the old infobox content
* @param newContent the new infobox content
* @return the full article content with the old infobox content replaced by the new infobox content
*/
public String insertInfoboxPartOfArticle(String originalArticleContent, String newContent) {
final Tuple2<String, String> beforeAndAfterOuterBalancedBrackets =
TemplateUtils.getBeforeAndAfterOuterBalancedBrackets(originalArticleContent, INFOBOX_HEADER);

return beforeAndAfterOuterBalancedBrackets._1() + newContent + beforeAndAfterOuterBalancedBrackets._2();
public Optional<String> insertInfoboxPartOfArticle(String originalArticleContent, String newContent) {
return TemplateUtils.getBeforeAndAfterOuterBalancedBrackets(originalArticleContent, INFOBOX_HEADER)
.map(beforeAndAfterOuterBalancedBrackets -> beforeAndAfterOuterBalancedBrackets._1() + newContent + beforeAndAfterOuterBalancedBrackets._2());
}
}
41 changes: 28 additions & 13 deletions src/main/java/com/tibiawiki/domain/factories/JsonFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -47,7 +40,7 @@ public class JsonFactory {
private static final List<String> ITEMS_WITH_NO_DROPPEDBY_LIST = Arrays.asList("Gold Coin", "Platinum Coin");
private static final String INFOBOX_HEADER_PATTERN = "\\{\\{Infobox[\\s|_](.*?)[\\||\\n]";
private static final String RARITY_PATTERN = "(always|common|uncommon|semi-rare|rare|very rare|extremely rare)(|\\?)";
private static final String LOOT_LINE_NAME_PATTERN = "(\\w+:\\d+)";
private static final String LOOT_LINE_NAME_PATTERN = "(\\w+:[\\d-]+)";
private static final String UNKNOWN = "Unknown";
private static final String RARITY = "rarity";
private static final String AMOUNT = "amount";
Expand Down Expand Up @@ -100,6 +93,23 @@ public JSONObject convertLootPartOfArticleToJson(String pageName, @Nullable fina
return enhanceLootJsonObject(new JSONObject(parametersAndValues));
}

@NotNull
public JSONObject convertAllLootPartsOfArticleToJson(String pageName, Map<String, String> lootPartsOfArticle) {

if (lootPartsOfArticle.isEmpty()) {
return new JSONObject();
}

var result = new JSONObject();

lootPartsOfArticle.forEach((k, v) -> {
var value = convertLootPartOfArticleToJson(pageName, v);
result.put(k, value);
});

return result;
}

@NotNull
public String convertJsonToInfoboxPartOfArticle(@Nullable JSONObject jsonObject, List<String> fieldOrder) {
if (jsonObject == null || jsonObject.isEmpty()) {
Expand Down Expand Up @@ -349,14 +359,19 @@ private JSONArray makeLootTableArray(String lootValue) {
return new JSONArray();
}

String lootItemsPartOfLootTable = TemplateUtils.getBetweenOuterBalancedBrackets(lootValue, "{{Loot Table");
lootItemsPartOfLootTable = TemplateUtils.removeFirstAndLastLine(lootItemsPartOfLootTable);
var lootItemsPartOfLootTable = TemplateUtils.getBetweenOuterBalancedBrackets(lootValue, "{{Loot Table");

if (lootItemsPartOfLootTable.isEmpty()) {
return new JSONArray();
}

var lootItemsPartOfLootTableStripped = TemplateUtils.removeFirstAndLastLine(lootItemsPartOfLootTable.get());

if (lootItemsPartOfLootTable.length() < 3) {
if (lootItemsPartOfLootTableStripped.length() < 3) {
return new JSONArray();
}

List<String> lootItemsList = Arrays.asList(Pattern.compile("(^|\n)(\\s|)\\|").split(lootItemsPartOfLootTable));
List<String> lootItemsList = Arrays.asList(Pattern.compile("(^|\n)(\\s|)\\|").split(lootItemsPartOfLootTableStripped));

for (String lootItemTemplate : lootItemsList) {
if (lootItemTemplate.length() < 1) {
Expand Down
Loading

0 comments on commit 916fdc7

Please sign in to comment.