mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-04-24 17:27:17 +00:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
fa43517b16 | |||
77e99439e7 | |||
c9eb311cfe | |||
d662e303c8 | |||
1c8aaea712 | |||
3c8e5f8da8 | |||
0bcda3233e | |||
c4be6f12f1 | |||
f3535f6e16 | |||
bdf413d494 | |||
7a1a73592e | |||
b1ac894ccf |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 18
|
||||||
- name: create dist
|
- name: create dist
|
||||||
run: make dist
|
run: make dist
|
||||||
- name: release
|
- name: release
|
||||||
|
4
.github/workflows/run-tests.yml
vendored
4
.github/workflows/run-tests.yml
vendored
@ -17,6 +17,6 @@ jobs:
|
|||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 18
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: make tests
|
run: make tests
|
||||||
|
3
TODO.md
3
TODO.md
@ -1,3 +0,0 @@
|
|||||||
* [x] items improvements
|
|
||||||
* [x] multiple parties support
|
|
||||||
* [ ] pretty UI
|
|
@ -2,7 +2,7 @@ organization := "me.arcanis"
|
|||||||
|
|
||||||
name := "ffxivbis"
|
name := "ffxivbis"
|
||||||
|
|
||||||
scalaVersion := "2.13.6"
|
scalaVersion := "2.13.12"
|
||||||
|
|
||||||
scalacOptions ++= Seq("-deprecation", "-feature")
|
scalacOptions ++= Seq("-deprecation", "-feature")
|
||||||
|
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
val AkkaVersion = "2.6.19"
|
val AkkaVersion = "2.8.6"
|
||||||
val AkkaHttpVersion = "10.2.9"
|
val AkkaHttpVersion = "10.5.3"
|
||||||
val ScalaTestVersion = "3.2.12"
|
val ScalaTestVersion = "3.2.19"
|
||||||
val SlickVersion = "3.3.3"
|
|
||||||
|
|
||||||
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11"
|
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.5.6"
|
||||||
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5"
|
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5"
|
||||||
|
|
||||||
libraryDependencies += "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion
|
libraryDependencies += "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion
|
||||||
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion
|
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion
|
||||||
libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion
|
libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion
|
||||||
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % AkkaVersion
|
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % AkkaVersion
|
||||||
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.7.0"
|
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.11.0"
|
||||||
libraryDependencies += "jakarta.platform" % "jakarta.jakartaee-web-api" % "9.1.0"
|
libraryDependencies += "jakarta.platform" % "jakarta.jakartaee-web-api" % "10.0.0"
|
||||||
libraryDependencies += "ch.megard" %% "akka-http-cors" % "1.1.3"
|
libraryDependencies += "ch.megard" %% "akka-http-cors" % "1.2.0"
|
||||||
|
|
||||||
libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"
|
libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"
|
||||||
|
|
||||||
libraryDependencies += "org.playframework.anorm" %% "anorm" % "2.6.10"
|
libraryDependencies += "org.playframework.anorm" %% "anorm" % "2.7.0"
|
||||||
libraryDependencies += "com.zaxxer" % "HikariCP" % "5.0.1" exclude("org.slf4j", "slf4j-api")
|
libraryDependencies += "com.zaxxer" % "HikariCP" % "5.1.0" exclude("org.slf4j", "slf4j-api")
|
||||||
libraryDependencies += "org.flywaydb" % "flyway-core" % "8.5.12"
|
libraryDependencies += "org.flywaydb" % "flyway-core" % "9.16.0"
|
||||||
libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.36.0.3"
|
libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.46.0.0"
|
||||||
libraryDependencies += "org.postgresql" % "postgresql" % "42.3.6"
|
libraryDependencies += "org.postgresql" % "postgresql" % "42.7.3"
|
||||||
|
|
||||||
libraryDependencies += "org.mindrot" % "jbcrypt" % "0.4"
|
libraryDependencies += "org.mindrot" % "jbcrypt" % "0.4"
|
||||||
libraryDependencies += "com.google.guava" % "guava" % "31.0.1-jre"
|
libraryDependencies += "com.google.guava" % "guava" % "31.0.1-jre"
|
||||||
|
@ -1 +1 @@
|
|||||||
sbt.version = 1.5.8
|
sbt.version = 1.7.1
|
||||||
|
@ -207,8 +207,8 @@
|
|||||||
}),
|
}),
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (_) { reload(); },
|
success: _ => { reload(); },
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
updateBisDialog.modal("hide");
|
updateBisDialog.modal("hide");
|
||||||
return true; // action expects boolean result
|
return true; // action expects boolean result
|
||||||
@ -247,9 +247,9 @@
|
|||||||
url: `/api/v1/party/${partyId}`,
|
url: `/api/v1/party/${partyId}`,
|
||||||
type: "GET",
|
type: "GET",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: response => {
|
||||||
const items = data.map(function (player) {
|
const items = response.map(player => {
|
||||||
return player.bis.map(function (loot) {
|
return player.bis.map(loot => {
|
||||||
return {
|
return {
|
||||||
nick: player.nick,
|
nick: player.nick,
|
||||||
job: player.job,
|
job: player.job,
|
||||||
@ -258,12 +258,12 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const payload = items.reduce(function (left, right) { return left.concat(right); }, []);
|
const payload = items.reduce((left, right) => { return left.concat(right); }, []);
|
||||||
table.bootstrapTable("load", payload);
|
table.bootstrapTable("load", payload);
|
||||||
table.bootstrapTable("uncheckAll");
|
table.bootstrapTable("uncheckAll");
|
||||||
table.bootstrapTable("hideLoading");
|
table.bootstrapTable("hideLoading");
|
||||||
|
|
||||||
const options = data.map(function (player) {
|
const options = response.map(player => {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.innerText = formatPlayerId(player);
|
option.innerText = formatPlayerId(player);
|
||||||
option.dataset.nick = player.nick;
|
option.dataset.nick = player.nick;
|
||||||
@ -272,13 +272,13 @@
|
|||||||
});
|
});
|
||||||
playerInput.empty().append(options);
|
playerInput.empty().append(options);
|
||||||
},
|
},
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePiece() {
|
function removePiece() {
|
||||||
const pieces = table.bootstrapTable("getSelections");
|
const pieces = table.bootstrapTable("getSelections");
|
||||||
pieces.map(function (loot) {
|
pieces.map(loot => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/api/v1/party/${partyId}/bis`,
|
url: `/api/v1/party/${partyId}/bis`,
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
@ -296,8 +296,8 @@
|
|||||||
}),
|
}),
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (_) { reload(); },
|
success: _ => { reload(); },
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -325,8 +325,8 @@
|
|||||||
}),
|
}),
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (_) { reload(); },
|
success: _ => { reload(); },
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
updateBisDialog.modal("hide");
|
updateBisDialog.modal("hide");
|
||||||
return true; // action expects boolean result
|
return true; // action expects boolean result
|
||||||
@ -342,7 +342,7 @@
|
|||||||
return false; // should not happen
|
return false; // should not happen
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(() => {
|
||||||
setupFormClear(updateBisDialog, reset);
|
setupFormClear(updateBisDialog, reset);
|
||||||
setupRemoveButton(table, removeButton);
|
setupRemoveButton(table, removeButton);
|
||||||
|
|
||||||
@ -353,8 +353,8 @@
|
|||||||
|
|
||||||
hideControls();
|
hideControls();
|
||||||
|
|
||||||
updateBisButton.click(function () { reset(); });
|
updateBisButton.click(() => { reset(); });
|
||||||
addPieceButton.click(function () { reset(); });
|
addPieceButton.click(() => { reset(); });
|
||||||
|
|
||||||
table.bootstrapTable({});
|
table.bootstrapTable({});
|
||||||
reload();
|
reload();
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
|
|
||||||
<div id="add-loot-dialog" tabindex="-1" role="dialog" class="modal fade">
|
<div id="add-loot-dialog" tabindex="-1" role="dialog" class="modal fade">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<form class="modal-content" action="javascript:" onsubmit="addLoot()">
|
<form class="modal-content" action="javascript:" onsubmit="addLootModal()">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">add looted piece</h4>
|
<h4 class="modal-title">add looted piece</h4>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||||
@ -132,6 +132,7 @@
|
|||||||
<table id="stats" class="table table-striped table-hover">
|
<table id="stats" class="table table-striped table-hover">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th data-formatter="addLootFormatter"></th>
|
||||||
<th data-field="nick">nick</th>
|
<th data-field="nick">nick</th>
|
||||||
<th data-field="job">job</th>
|
<th data-field="job">job</th>
|
||||||
<th data-field="isRequired">required</th>
|
<th data-field="isRequired">required</th>
|
||||||
@ -198,30 +199,40 @@
|
|||||||
const pieceTypeInput = $("#piece-type");
|
const pieceTypeInput = $("#piece-type");
|
||||||
const playerInput = $("#player");
|
const playerInput = $("#player");
|
||||||
|
|
||||||
function addLoot() {
|
function addLoot(nick, job) {
|
||||||
const player = getCurrentOption(playerInput);
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/api/v1/party/${partyId}/loot`,
|
url: `/api/v1/party/${partyId}/loot`,
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
action: "add",
|
action: "add",
|
||||||
piece: {
|
piece: {
|
||||||
pieceType: pieceTypeInput.val(),
|
pieceType: pieceTypeInput.val(),
|
||||||
job: player.dataset.job,
|
job: job,
|
||||||
piece: pieceInput.val(),
|
piece: pieceInput.val(),
|
||||||
},
|
},
|
||||||
playerId: {
|
playerId: {
|
||||||
partyId: partyId,
|
partyId: partyId,
|
||||||
nick: player.dataset.nick,
|
nick: nick,
|
||||||
job: player.dataset.job,
|
job: job,
|
||||||
},
|
},
|
||||||
isFreeLoot: freeLootInput.is(":checked"),
|
isFreeLoot: freeLootInput.is(":checked"),
|
||||||
}),
|
}),
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (_) { reload(); },
|
success: _ => {
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
addLootDialog.modal("hide");
|
||||||
|
reload();
|
||||||
|
},
|
||||||
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
addLootDialog.modal("hide");
|
}
|
||||||
|
|
||||||
|
function addLootFormatter(value, row, index) {
|
||||||
|
return `<button type="button" class="btn btn-primary" onclick="addLoot('${row.nick}', '${row.job}')"><i class="bi bi-plus"></i></button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLootModal() {
|
||||||
|
const player = getCurrentOption(playerInput);
|
||||||
|
addLoot(player.dataset.nick, player.dataset.job);
|
||||||
return true; // action expects boolean result
|
return true; // action expects boolean result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,9 +247,9 @@
|
|||||||
url: `/api/v1/party/${partyId}`,
|
url: `/api/v1/party/${partyId}`,
|
||||||
type: "GET",
|
type: "GET",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: response => {
|
||||||
const items = data.map(function (player) {
|
const items = response.map(player => {
|
||||||
return player.loot.map(function (loot) {
|
return player.loot.map(loot => {
|
||||||
return {
|
return {
|
||||||
nick: player.nick,
|
nick: player.nick,
|
||||||
job: player.job,
|
job: player.job,
|
||||||
@ -249,12 +260,12 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const payload = items.reduce(function (left, right) { return left.concat(right); }, []);
|
const payload = items.reduce((left, right) => { return left.concat(right); }, []);
|
||||||
table.bootstrapTable("load", payload);
|
table.bootstrapTable("load", payload);
|
||||||
table.bootstrapTable("uncheckAll");
|
table.bootstrapTable("uncheckAll");
|
||||||
table.bootstrapTable("hideLoading");
|
table.bootstrapTable("hideLoading");
|
||||||
|
|
||||||
const options = data.map(function (player) {
|
const options = response.map(player => {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.innerText = formatPlayerId(player);
|
option.innerText = formatPlayerId(player);
|
||||||
option.dataset.nick = player.nick;
|
option.dataset.nick = player.nick;
|
||||||
@ -263,13 +274,13 @@
|
|||||||
});
|
});
|
||||||
playerInput.empty().append(options);
|
playerInput.empty().append(options);
|
||||||
},
|
},
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeLoot() {
|
function removeLoot() {
|
||||||
const pieces = table.bootstrapTable("getSelections");
|
const pieces = table.bootstrapTable("getSelections");
|
||||||
pieces.map(function (loot) {
|
pieces.map(loot => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/api/v1/party/${partyId}/loot`,
|
url: `/api/v1/party/${partyId}/loot`,
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
@ -288,8 +299,8 @@
|
|||||||
}),
|
}),
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (_) { reload(); },
|
success: _ => { reload(); },
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -306,8 +317,8 @@
|
|||||||
type: "PUT",
|
type: "PUT",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: response => {
|
||||||
const payload = data.map(function (stat) {
|
const payload = response.map(stat => {
|
||||||
return {
|
return {
|
||||||
nick: stat.nick,
|
nick: stat.nick,
|
||||||
job: stat.job,
|
job: stat.job,
|
||||||
@ -321,11 +332,11 @@
|
|||||||
stats.bootstrapTable("uncheckAll");
|
stats.bootstrapTable("uncheckAll");
|
||||||
stats.bootstrapTable("hideLoading");
|
stats.bootstrapTable("hideLoading");
|
||||||
},
|
},
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(() => {
|
||||||
setupFormClear(addLootDialog);
|
setupFormClear(addLootDialog);
|
||||||
setupRemoveButton(table, removeButton);
|
setupRemoveButton(table, removeButton);
|
||||||
|
|
||||||
|
@ -184,8 +184,8 @@
|
|||||||
}),
|
}),
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (_) { reload(); },
|
success: _ => { reload(); },
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
addPlayerDialog.modal("hide");
|
addPlayerDialog.modal("hide");
|
||||||
return true; // action expects boolean result
|
return true; // action expects boolean result
|
||||||
@ -210,18 +210,18 @@
|
|||||||
url: `/api/v1/party/${partyId}`,
|
url: `/api/v1/party/${partyId}`,
|
||||||
type: "GET",
|
type: "GET",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: response => {
|
||||||
table.bootstrapTable("load", data);
|
table.bootstrapTable("load", response);
|
||||||
table.bootstrapTable("uncheckAll");
|
table.bootstrapTable("uncheckAll");
|
||||||
table.bootstrapTable("hideLoading");
|
table.bootstrapTable("hideLoading");
|
||||||
},
|
},
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePlayers() {
|
function removePlayers() {
|
||||||
const players = table.bootstrapTable("getSelections");
|
const players = table.bootstrapTable("getSelections");
|
||||||
players.map(function (player) {
|
players.map(player => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/api/v1/party/${partyId}`,
|
url: `/api/v1/party/${partyId}`,
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
@ -234,13 +234,13 @@
|
|||||||
}),
|
}),
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (_) { reload(); },
|
success: _ => { reload(); },
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(() => {
|
||||||
setupFormClear(addPlayerDialog);
|
setupFormClear(addPlayerDialog);
|
||||||
setupRemoveButton(table, removeButton);
|
setupRemoveButton(table, removeButton);
|
||||||
|
|
||||||
|
@ -173,8 +173,8 @@
|
|||||||
}),
|
}),
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function (_) { reload(); },
|
success: _ => { reload(); },
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
addUserDialog.modal("hide");
|
addUserDialog.modal("hide");
|
||||||
return true; // action expects boolean result
|
return true; // action expects boolean result
|
||||||
@ -191,28 +191,28 @@
|
|||||||
url: `/api/v1/party/${partyId}/users`,
|
url: `/api/v1/party/${partyId}/users`,
|
||||||
type: "GET",
|
type: "GET",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: response => {
|
||||||
table.bootstrapTable("load", data);
|
table.bootstrapTable("load", response);
|
||||||
table.bootstrapTable("uncheckAll");
|
table.bootstrapTable("uncheckAll");
|
||||||
table.bootstrapTable("hideLoading");
|
table.bootstrapTable("hideLoading");
|
||||||
},
|
},
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeUsers() {
|
function removeUsers() {
|
||||||
const users = table.bootstrapTable("getSelections");
|
const users = table.bootstrapTable("getSelections");
|
||||||
users.map(function (user) {
|
users.map(user => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/api/v1/party/${partyId}/users/${user.username}`,
|
url: `/api/v1/party/${partyId}/users/${user.username}`,
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
success: function (_) { reload(); },
|
success: _ => { reload(); },
|
||||||
error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
|
error: (jqXHR, _, errorThrown) => { requestAlert(jqXHR, errorThrown); },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(() => {
|
||||||
setupFormClear(addUserDialog);
|
setupFormClear(addUserDialog);
|
||||||
setupRemoveButton(table, removeButton);
|
setupRemoveButton(table, removeButton);
|
||||||
|
|
||||||
|
@ -16,12 +16,12 @@ import java.time.Instant
|
|||||||
|
|
||||||
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
|
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
|
||||||
|
|
||||||
private def enumFormat[E <: Enumeration](enum: E): RootJsonFormat[E#Value] =
|
private def enumFormat[E <: Enumeration](enumeration: E): RootJsonFormat[E#Value] =
|
||||||
new RootJsonFormat[E#Value] {
|
new RootJsonFormat[E#Value] {
|
||||||
override def write(obj: E#Value): JsValue = obj.toString.toJson
|
override def write(obj: E#Value): JsValue = obj.toString.toJson
|
||||||
override def read(json: JsValue): E#Value = json match {
|
override def read(json: JsValue): E#Value = json match {
|
||||||
case JsNumber(value) => enum(value.toInt)
|
case JsNumber(value) => enumeration(value.toInt)
|
||||||
case JsString(name) => enum.withName(name)
|
case JsString(name) => enumeration.withName(name)
|
||||||
case other => deserializationError(s"String or number expected, got $other")
|
case other => deserializationError(s"String or number expected, got $other")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,6 +95,7 @@ object Job {
|
|||||||
case object RPR extends Drgs
|
case object RPR extends Drgs
|
||||||
case object NIN extends Nins
|
case object NIN extends Nins
|
||||||
case object SAM extends Mnks
|
case object SAM extends Mnks
|
||||||
|
case object VPR extends Mnks
|
||||||
|
|
||||||
case object BRD extends Ranges
|
case object BRD extends Ranges
|
||||||
case object MCH extends Ranges
|
case object MCH extends Ranges
|
||||||
@ -103,9 +104,10 @@ object Job {
|
|||||||
case object BLM extends Casters
|
case object BLM extends Casters
|
||||||
case object SMN extends Casters
|
case object SMN extends Casters
|
||||||
case object RDM extends Casters
|
case object RDM extends Casters
|
||||||
|
case object PCT extends Casters
|
||||||
|
|
||||||
val available: Seq[Job] =
|
val available: Seq[Job] =
|
||||||
Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, SGE, MNK, DRG, RPR, NIN, SAM, BRD, MCH, DNC, BLM, SMN, RDM)
|
Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, SGE, MNK, DRG, RPR, NIN, SAM, VPR, BRD, MCH, DNC, BLM, SMN, RDM, PCT)
|
||||||
val availableWithAnyJob: Seq[Job] = available.prepended(AnyJob)
|
val availableWithAnyJob: Seq[Job] = available.prepended(AnyJob)
|
||||||
|
|
||||||
def withName(job: String): Job =
|
def withName(job: String): Job =
|
||||||
|
@ -113,7 +113,7 @@ object Piece {
|
|||||||
case "weapon" => Weapon(pieceType, job)
|
case "weapon" => Weapon(pieceType, job)
|
||||||
case "head" => Head(pieceType, job)
|
case "head" => Head(pieceType, job)
|
||||||
case "body" => Body(pieceType, job)
|
case "body" => Body(pieceType, job)
|
||||||
case "hands" => Hands(pieceType, job)
|
case "hand" | "hands" => Hands(pieceType, job)
|
||||||
case "legs" => Legs(pieceType, job)
|
case "legs" => Legs(pieceType, job)
|
||||||
case "feet" => Feet(pieceType, job)
|
case "feet" => Feet(pieceType, job)
|
||||||
case "ears" => Ears(pieceType, job)
|
case "ears" => Ears(pieceType, job)
|
||||||
|
@ -16,7 +16,7 @@ import com.typesafe.scalalogging.StrictLogging
|
|||||||
import me.arcanis.ffxivbis.messages.BiSProviderMessage
|
import me.arcanis.ffxivbis.messages.BiSProviderMessage
|
||||||
import me.arcanis.ffxivbis.models.{BiS, Job, Piece, PieceType}
|
import me.arcanis.ffxivbis.models.{BiS, Job, Piece, PieceType}
|
||||||
import me.arcanis.ffxivbis.service.bis.parser.Parser
|
import me.arcanis.ffxivbis.service.bis.parser.Parser
|
||||||
import me.arcanis.ffxivbis.service.bis.parser.impl.{Ariyala, Etro}
|
import me.arcanis.ffxivbis.service.bis.parser.impl.{Ariyala, Etro, XIVGear}
|
||||||
import spray.json._
|
import spray.json._
|
||||||
|
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
@ -52,7 +52,10 @@ class BisProvider(context: ActorContext[BiSProviderMessage])
|
|||||||
val url = Uri(link)
|
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 else Ariyala
|
val parser =
|
||||||
|
if (url.authority.host.address().contains("etro")) Etro
|
||||||
|
else if (url.authority.host.address().contains("xivgear.app")) XIVGear
|
||||||
|
else Ariyala
|
||||||
val uri = parser.uri(url, id)
|
val uri = parser.uri(url, id)
|
||||||
sendRequest(uri, BisProvider.parseBisJsonToPieces(job, parser, getPieceType))
|
sendRequest(uri, BisProvider.parseBisJsonToPieces(job, parser, getPieceType))
|
||||||
} catch {
|
} catch {
|
||||||
@ -81,12 +84,11 @@ object BisProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def remapKey(key: String): Option[String] = key match {
|
def remapKey(key: String): Option[String] = Some(key.toLowerCase).collect {
|
||||||
case "mainhand" => Some("weapon")
|
case "mainhand" => "weapon"
|
||||||
case "chest" => Some("body")
|
case "chest" => "body"
|
||||||
case "ringLeft" | "fingerL" => Some("left ring")
|
case "ringleft" | "fingerl" => "left ring"
|
||||||
case "ringRight" | "fingerR" => Some("right ring")
|
case "ringright" | "fingerr" => "right ring"
|
||||||
case "weapon" | "head" | "body" | "hands" | "legs" | "feet" | "ears" | "neck" | "wrist" | "wrists" => Some(key)
|
case "weapon" | "head" | "body" | "hand" | "hands" | "legs" | "feet" | "ears" | "neck" | "wrist" | "wrists" => key
|
||||||
case _ => None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import spray.json._
|
|||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import scala.jdk.CollectionConverters._
|
import scala.jdk.CollectionConverters._
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
import scala.util.matching.Regex
|
||||||
|
|
||||||
trait XivApi extends RequestExecutor {
|
trait XivApi extends RequestExecutor {
|
||||||
|
|
||||||
@ -75,6 +76,10 @@ trait XivApi extends RequestExecutor {
|
|||||||
|
|
||||||
object XivApi {
|
object XivApi {
|
||||||
|
|
||||||
|
private val defaultShop = JsObject("IsUnique" -> JsNumber(1), "StackSize" -> JsNumber(999))
|
||||||
|
|
||||||
|
private val itemRegexp = new Regex("""Item(Receive|Cost)(\d+)""", "type", "index")
|
||||||
|
|
||||||
private def parseXivapiJsonToShop(
|
private def parseXivapiJsonToShop(
|
||||||
js: JsObject
|
js: JsObject
|
||||||
)(implicit executionContext: ExecutionContext): Future[Map[Long, (String, Long)]] = {
|
)(implicit executionContext: ExecutionContext): Future[Map[Long, (String, Long)]] = {
|
||||||
@ -84,12 +89,12 @@ object XivApi {
|
|||||||
.map(_ => "crafted" -> -1L) // you can craft this item
|
.map(_ => "crafted" -> -1L) // you can craft this item
|
||||||
.orElse { // lets try shop items
|
.orElse { // lets try shop items
|
||||||
js.fields("SpecialShop").asJsObject.fields.collectFirst {
|
js.fields("SpecialShop").asJsObject.fields.collectFirst {
|
||||||
case (shopName, JsArray(array)) if shopName.startsWith("ItemReceive") =>
|
case (shopName, JsArray(array)) if itemRegexp.matches(shopName) =>
|
||||||
val shopId = array.head match {
|
val shopId = array.head match {
|
||||||
case JsNumber(id) => id.toLong
|
case JsNumber(id) => id.toLong
|
||||||
case other => throw deserializationError(s"Could not parse $other")
|
case other => throw deserializationError(s"Could not parse $other")
|
||||||
}
|
}
|
||||||
shopName.replace("ItemReceive", "") -> shopId
|
itemRegexp.findFirstMatchIn(shopName).get.group("index") -> shopId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.getOrElse(throw deserializationError(s"Could not parse $js"))
|
.getOrElse(throw deserializationError(s"Could not parse $js"))
|
||||||
@ -128,7 +133,7 @@ object XivApi {
|
|||||||
if (index == "crafted" && shopId == -1L) PieceType.Crafted
|
if (index == "crafted" && shopId == -1L) PieceType.Crafted
|
||||||
else
|
else
|
||||||
Try(shopMap(shopId).fields(s"ItemCost$index").asJsObject)
|
Try(shopMap(shopId).fields(s"ItemCost$index").asJsObject)
|
||||||
.getOrElse(throw new Exception(s"${shopMap(shopId).fields(s"ItemCost$index")}, $index"))
|
.getOrElse(defaultShop)
|
||||||
.getFields("IsUnique", "StackSize") match {
|
.getFields("IsUnique", "StackSize") match {
|
||||||
case Seq(JsNumber(isUnique), JsNumber(stackSize)) =>
|
case Seq(JsNumber(isUnique), JsNumber(stackSize)) =>
|
||||||
if (isUnique == 1 || stackSize.toLong != 999) PieceType.Tome // either upgraded gear or tomes found
|
if (isUnique == 1 || stackSize.toLong != 999) PieceType.Tome // either upgraded gear or tomes found
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2022 Evgeniy Alekseev.
|
||||||
|
*
|
||||||
|
* This file is part of ffxivbis
|
||||||
|
* (see https://github.com/arcan1s/ffxivbis).
|
||||||
|
*
|
||||||
|
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package me.arcanis.ffxivbis.service.bis.parser.impl
|
||||||
|
|
||||||
|
import akka.http.scaladsl.model.Uri
|
||||||
|
import me.arcanis.ffxivbis.models.Job
|
||||||
|
import me.arcanis.ffxivbis.service.bis.BisProvider
|
||||||
|
import me.arcanis.ffxivbis.service.bis.parser.Parser
|
||||||
|
import spray.json.{deserializationError, JsNumber, JsObject}
|
||||||
|
|
||||||
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
|
object XIVGear extends Parser {
|
||||||
|
|
||||||
|
override def parse(job: Job, js: JsObject)(implicit executionContext: ExecutionContext): Future[Map[String, Long]] =
|
||||||
|
Future {
|
||||||
|
val set = js.fields.get("items") match {
|
||||||
|
case Some(JsObject(items)) => items
|
||||||
|
case other => throw deserializationError(s"Invalid job name $other")
|
||||||
|
}
|
||||||
|
set.foldLeft(Map.empty[String, Long]) {
|
||||||
|
case (acc, (key, JsObject(properties))) =>
|
||||||
|
val pieceId = properties.get("id").collect { case JsNumber(id) =>
|
||||||
|
id.toLong
|
||||||
|
}
|
||||||
|
(for (
|
||||||
|
piece <- BisProvider.remapKey(key);
|
||||||
|
id <- pieceId
|
||||||
|
) yield (piece, id)).map(acc + _).getOrElse(acc)
|
||||||
|
case (acc, _) => acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def uri(root: Uri, id: String): Uri = {
|
||||||
|
val gearSet = Uri(id).query().get("page").map(_.replace("sl|", "")).getOrElse(id)
|
||||||
|
root.withHost(s"api.${root.authority.host.address()}").withPath(Uri.Path / "shortlink" / gearSet)
|
||||||
|
}
|
||||||
|
}
|
@ -52,12 +52,28 @@ object Fixtures {
|
|||||||
Piece.Ring(pieceType = PieceType.Tome, Job.SGE, "right ring")
|
Piece.Ring(pieceType = PieceType.Tome, Job.SGE, "right ring")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
lazy val bis4: BiS = BiS(
|
||||||
|
Seq(
|
||||||
|
Piece.Weapon(pieceType = PieceType.Savage ,Job.VPR),
|
||||||
|
Piece.Head(pieceType = PieceType.Savage, Job.VPR),
|
||||||
|
Piece.Body(pieceType = PieceType.Savage, Job.VPR),
|
||||||
|
Piece.Hands(pieceType = PieceType.Tome, Job.VPR),
|
||||||
|
Piece.Legs(pieceType = PieceType.Tome, Job.VPR),
|
||||||
|
Piece.Feet(pieceType = PieceType.Savage, Job.VPR),
|
||||||
|
Piece.Ears(pieceType = PieceType.Tome, Job.VPR),
|
||||||
|
Piece.Neck(pieceType = PieceType.Savage, Job.VPR),
|
||||||
|
Piece.Wrist(pieceType = PieceType.Tome, Job.VPR),
|
||||||
|
Piece.Ring(pieceType = PieceType.Savage, Job.VPR, "left ring"),
|
||||||
|
Piece.Ring(pieceType = PieceType.Tome, Job.VPR, "right ring")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
lazy val link: String = "https://ffxiv.ariyala.com/19V5R"
|
lazy val link: String = "https://ffxiv.ariyala.com/19V5R"
|
||||||
lazy val link2: String = "https://ffxiv.ariyala.com/1A0WM"
|
lazy val link2: String = "https://ffxiv.ariyala.com/1A0WM"
|
||||||
lazy val link3: String = "https://etro.gg/gearset/26a67536-b4ce-4adc-a46a-f70e348bb138"
|
lazy val link3: String = "https://etro.gg/gearset/26a67536-b4ce-4adc-a46a-f70e348bb138"
|
||||||
lazy val link4: String = "https://etro.gg/gearset/865fc886-994f-4c28-8fc1-4379f160a916"
|
lazy val link4: String = "https://etro.gg/gearset/865fc886-994f-4c28-8fc1-4379f160a916"
|
||||||
lazy val link5: String = "https://ffxiv.ariyala.com/1FGU0"
|
lazy val link5: String = "https://ffxiv.ariyala.com/1FGU0"
|
||||||
|
lazy val link6: String = "https://xivgear.app/?page=sl%7Cd65b4776-01e1-4269-af74-0bc6e01ca2ec"
|
||||||
|
|
||||||
lazy val lootWeapon: Piece = Piece.Weapon(pieceType = PieceType.Tome, Job.AnyJob)
|
lazy val lootWeapon: Piece = Piece.Weapon(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
lazy val lootBody: Piece = Piece.Body(pieceType = PieceType.Savage, Job.AnyJob)
|
lazy val lootBody: Piece = Piece.Body(pieceType = PieceType.Savage, Job.AnyJob)
|
||||||
|
@ -41,5 +41,11 @@ class BisProviderTest extends ScalaTestWithActorTestKit(Settings.withRandomDatab
|
|||||||
probe.expectMessage(askTimeout, Fixtures.bis2)
|
probe.expectMessage(askTimeout, Fixtures.bis2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"get best in slot set (xivgear)" in {
|
||||||
|
val probe = testKit.createTestProbe[BiS]()
|
||||||
|
provider ! DownloadBiS(Fixtures.link6, Job.VPR, probe.ref)
|
||||||
|
probe.expectMessage(askTimeout, Fixtures.bis4)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
version := "0.13.6"
|
version := "0.15.4"
|
||||||
|
Loading…
Reference in New Issue
Block a user