3 Commits

Author SHA1 Message Date
cdbb9d9aa5 Release 0.15.3 2024-07-31 15:43:30 +03:00
2d6445447b bug: remove unreachable code 2024-07-31 15:43:17 +03:00
57e65e1901 Release 0.15.2 2024-07-31 15:41:39 +03:00
9 changed files with 99 additions and 52 deletions

View File

@ -12,7 +12,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: extract version
id: version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
@ -22,24 +21,13 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
filter: 'Release \d+\.\d+\.\d+'
- name: setup JDK
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 18
- name: install sbt
uses: eclipse-score/apt-install@main
with:
packages: wget
- run: |
wget https://scala.jfrog.io/artifactory/debian/sbt-1.11.7.deb
sudo dpkg -i sbt-1.11.7.deb
- name: create dist
run: make dist
- name: release
uses: softprops/action-gh-release@v1
with:

View File

@ -13,20 +13,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: setup JDK
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 18
- name: install sbt
uses: eclipse-score/apt-install@main
with:
packages: wget
- run: |
wget https://scala.jfrog.io/artifactory/debian/sbt-1.11.7.deb
sudo dpkg -i sbt-1.11.7.deb
- name: run tests
run: make tests

View File

@ -41,7 +41,7 @@
<div class="container">
<div id="toolbar">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-loot-dialog" hidden>
<button id="add-btn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-loot-dialog" hidden>
<i class="bi bi-plus"></i> add
</button>
<button class="btn btn-secondary" onclick="reload()">
@ -147,7 +147,7 @@
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-bs-dismiss="modal">close</button>
<button type="button" class="btn btn-secondary" onclick="suggestLoot()">suggest</button>
<button id="add-btn" type="submit" class="btn btn-primary">add</button>
<button type="submit" class="btn btn-primary">add</button>
</div>
</form>
</div>

View File

@ -4,7 +4,7 @@ me.arcanis.ffxivbis {
include "item_data.json"
# xivapi base url, string, required
xivapi-url = "https://v2.xivapi.com"
xivapi-url = "https://xivapi.com"
# xivapi developer key, string, optional
#xivapi-key = "abcdef"
}

View File

@ -12,5 +12,5 @@ trait ValidatorHelper {
object ValidatorHelper {
final val VALID_CHARACTERS = HashSet.from("!@#$%^&*()-_=+;:',./?| ")
final val VALID_CHARACTERS = HashSet.from("!@#$%^&*()-_=+;:',./? ")
}

View File

@ -50,7 +50,7 @@ class BisProvider(context: ActorContext[BiSProviderMessage])
private def get(link: String, job: Job): Future[Seq[Piece]] =
try {
val url = Uri(link)
val id = Paths.get(link).normalize.getFileName.toString
val id = Paths.get(link).normalize.getFileName.toString
val parser =
if (url.authority.host.address().contains("etro")) Etro

View File

@ -15,6 +15,7 @@ import spray.json._
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.CollectionConverters._
import scala.util.Try
import scala.util.matching.Regex
trait XivApi extends RequestExecutor {
@ -40,38 +41,106 @@ trait XivApi extends RequestExecutor {
else remotePieceType(remote).map(_ ++ local)
}
private def remotePieceType(itemIds: Seq[Long]): Future[Map[Long, PieceType]] =
Future
.traverse(itemIds) { id =>
val uriForItem = Uri(xivapiUrl)
.withPath(Uri.Path / "api" / "sheet" / "Item" / id.toString)
.withQuery(
Uri.Query(
Map(
"fields" -> Seq("Lot").mkString(","),
"private_key" -> xivapiKey.getOrElse("")
)
private def remotePieceType(itemIds: Seq[Long]): Future[Map[Long, PieceType]] = {
val uriForItems = Uri(xivapiUrl)
.withPath(Uri.Path / "item")
.withQuery(
Uri.Query(
Map(
"columns" -> Seq("ID", "GameContentLinks").mkString(","),
"ids" -> itemIds.mkString(","),
"private_key" -> xivapiKey.getOrElse("")
)
)
)
sendRequest(uriForItems, XivApi.parseXivapiJsonToShop).flatMap { shops =>
val shopIds = shops.values.map(_._2).toSet
val columns = shops.values.map(pair => s"ItemCost${pair._1}").toSet
val uriForShops = Uri(xivapiUrl)
.withPath(Uri.Path / "specialshop")
.withQuery(
Uri.Query(
Map(
"columns" -> (columns + "ID").mkString(","),
"ids" -> shopIds.mkString(","),
"private_key" -> xivapiKey.getOrElse("")
)
)
)
sendRequest(uriForItem, XivApi.parseXivapiJsonToLot).map(id -> _)
}
.map(_.toMap)
sendRequest(uriForShops, XivApi.parseXivapiJsonToType(shops))
}
}
}
object XivApi {
private def parseXivapiJsonToLot(js: JsObject)(implicit executionContext: ExecutionContext): Future[PieceType] =
private val defaultShop = JsObject("IsUnique" -> JsNumber(1), "StackSize" -> JsNumber(999))
private val itemRegexp = new Regex("""Item(Receive|Cost)(\d+)""", "type", "index")
private def parseXivapiJsonToShop(
js: JsObject
)(implicit executionContext: ExecutionContext): Future[Map[Long, (String, Long)]] = {
def extractTraderId(js: JsObject) =
js.fields
.get("Recipe")
.map(_ => "crafted" -> -1L) // you can craft this item
.orElse { // lets try shop items
js.fields("SpecialShop").asJsObject.fields.collectFirst {
case (shopName, JsArray(array)) if itemRegexp.matches(shopName) =>
val shopId = array.head match {
case JsNumber(id) => id.toLong
case other => throw deserializationError(s"Could not parse $other")
}
itemRegexp.findFirstMatchIn(shopName).get.group("index") -> shopId
}
}
.getOrElse(throw deserializationError(s"Could not parse $js"))
Future {
js.fields("fields") match {
case JsObject(fields) =>
fields
.get("Lot")
.collect {
case JsBoolean(true) => PieceType.Savage
case JsBoolean(false) => PieceType.Tome
js.fields("Results") match {
case array: JsArray =>
array.elements
.map(_.asJsObject.getFields("ID", "GameContentLinks") match {
case Seq(JsNumber(id), shop: JsObject) => id.toLong -> extractTraderId(shop.asJsObject)
case other => throw deserializationError(s"Could not parse $other")
})
.toMap
case other => throw deserializationError(s"Could not parse $other")
}
}
}
private def parseXivapiJsonToType(
shops: Map[Long, (String, Long)]
)(js: JsObject)(implicit executionContext: ExecutionContext): Future[Map[Long, PieceType]] =
Future {
val shopMap = js.fields("Results") match {
case array: JsArray =>
array.elements.collect { case shop: JsObject =>
shop.fields("ID") match {
case JsNumber(id) => id.toLong -> shop
case other => throw deserializationError(s"Could not parse $other")
}
.getOrElse(throw deserializationError(s"Could not find lot field in $fields"))
}.toMap
case other => throw deserializationError(s"Could not parse $other")
}
shops.map { case (itemId, (index, shopId)) =>
val pieceType =
if (index == "crafted" && shopId == -1L) PieceType.Crafted
else
Try(shopMap(shopId).fields(s"ItemCost$index").asJsObject)
.getOrElse(defaultShop)
.getFields("IsUnique", "StackSize") match {
case Seq(JsNumber(isUnique), JsNumber(stackSize)) =>
if (isUnique == 1 || stackSize.toLong != 999) PieceType.Tome // either upgraded gear or tomes found
else PieceType.Savage
case other => throw deserializationError(s"Could not parse $other")
}
itemId -> pieceType
}
}
}

View File

@ -35,7 +35,7 @@ class BisProviderTest extends ScalaTestWithActorTestKit(Settings.withRandomDatab
probe.expectMessage(askTimeout, Fixtures.bis)
}
"get best in slot set (etro 2)" ignore {
"get best in slot set (etro 2)" in {
val probe = testKit.createTestProbe[BiS]()
provider ! DownloadBiS(Fixtures.link4, Job.DNC, probe.ref)
probe.expectMessage(askTimeout, Fixtures.bis2)

View File

@ -1 +1 @@
version := "0.15.6"
version := "0.15.3"