mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-28 17:01:02 +00:00
Minor: Additional functions (#25)
* several functions. fixes #1 and #2 * multi selection functionality
This commit is contained in:
@@ -16,6 +16,9 @@ namespace ccm {
|
||||
struct YuGiOhCatalogCard {
|
||||
std::string setNo;
|
||||
std::string name;
|
||||
/// YGOPRODeck `set_rarity` for this printing when known (optional;
|
||||
/// older `set-catalog.json` files omit it).
|
||||
std::string rarity{};
|
||||
|
||||
friend bool operator==(const YuGiOhCatalogCard&,
|
||||
const YuGiOhCatalogCard&) = default;
|
||||
|
||||
@@ -35,6 +35,12 @@ public:
|
||||
Result<std::vector<AutoDetectedPrint>> detectPrintVariants(std::string_view name,
|
||||
std::string_view setName) override;
|
||||
|
||||
Result<AutoDetectedPrint> detectBySetNo(std::string_view setName,
|
||||
std::string_view setNo) override;
|
||||
Result<std::vector<AutoDetectedPrint>> detectVariantsBySetNo(
|
||||
std::string_view setName,
|
||||
std::string_view setNo) override;
|
||||
|
||||
// Uppercase the alphabetic prefix of a Digi-Battle card number (bo-88 -> BO-88).
|
||||
// Does not invent zero-padding — CDN keys match API ids literally.
|
||||
static std::string normalizeCardNumber(std::string_view setNo);
|
||||
@@ -58,7 +64,8 @@ public:
|
||||
static Result<std::vector<AutoDetectedPrint>>
|
||||
parsePrintVariants(const std::string& body,
|
||||
std::string_view setName,
|
||||
std::string_view wantedCardName);
|
||||
std::string_view wantedCardName,
|
||||
std::string_view wantedSetNo = {});
|
||||
|
||||
private:
|
||||
IHttpClient& http_;
|
||||
|
||||
@@ -29,6 +29,12 @@ public:
|
||||
Result<std::vector<AutoDetectedPrint>> detectPrintVariants(std::string_view name,
|
||||
std::string_view setId) override;
|
||||
|
||||
Result<AutoDetectedPrint> detectBySetNo(std::string_view setId,
|
||||
std::string_view setNo) override;
|
||||
Result<std::vector<AutoDetectedPrint>> detectVariantsBySetNo(
|
||||
std::string_view setId,
|
||||
std::string_view setNo) override;
|
||||
|
||||
// Strip everything after the first '/' (e.g. "4/102" -> "4").
|
||||
static std::string normalizeCollectorNumber(std::string_view setNo);
|
||||
|
||||
@@ -61,6 +67,9 @@ public:
|
||||
std::string_view setId,
|
||||
std::string_view wantedCardName);
|
||||
|
||||
// Parse TCGdex card-by-id JSON into print metadata (name + localId + rarity).
|
||||
static Result<AutoDetectedPrint> parsePrintFromCardById(const std::string& body);
|
||||
|
||||
private:
|
||||
IHttpClient& http_;
|
||||
};
|
||||
|
||||
@@ -29,6 +29,12 @@ public:
|
||||
Result<std::vector<AutoDetectedPrint>> detectPrintVariants(std::string_view name,
|
||||
std::string_view setId) override;
|
||||
|
||||
Result<AutoDetectedPrint> detectBySetNo(std::string_view setId,
|
||||
std::string_view setNo) override;
|
||||
Result<std::vector<AutoDetectedPrint>> detectVariantsBySetNo(
|
||||
std::string_view setId,
|
||||
std::string_view setNo) override;
|
||||
|
||||
static std::string normalizeLocalId(std::string_view setNo);
|
||||
static std::string buildSetDetailUrl(std::string_view setId);
|
||||
static std::string buildCardUrl(std::string_view setId, std::string_view localId);
|
||||
@@ -60,6 +66,15 @@ public:
|
||||
std::string_view wantedCardName,
|
||||
const JapanesePokemonEnCatalog& catalog);
|
||||
|
||||
// Reverse lookup: set + localId → name via catalog (no HTTP).
|
||||
static Result<std::vector<AutoDetectedPrint>>
|
||||
detectVariantsBySetNoFromCatalog(std::string_view setId,
|
||||
std::string_view localId,
|
||||
const JapanesePokemonEnCatalog& catalog);
|
||||
|
||||
// Parse TCGdex JA card-by-id JSON into print metadata.
|
||||
static Result<AutoDetectedPrint> parsePrintFromCardResponse(const std::string& body);
|
||||
|
||||
private:
|
||||
IHttpClient& http_;
|
||||
const JapanesePokemonEnCatalog& catalog_;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ccm/domain/YuGiOhSetCatalog.hpp"
|
||||
#include "ccm/ports/ICardPreviewSource.hpp"
|
||||
#include "ccm/ports/IHttpClient.hpp"
|
||||
#include "ccm/services/YuGiOhSetCatalogService.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -25,6 +28,9 @@ namespace ccm {
|
||||
// that endpoint returns a richer set listing (with rarities and release
|
||||
// dates) than Yugipedia, and we don't need image data for it.
|
||||
//
|
||||
// Reverse lookup (set + setNo → name) uses the offline set-completion catalog
|
||||
// written by Sets → Update Yu-Gi-Oh! (`YuGiOhSetCatalogService`).
|
||||
//
|
||||
// Region policy: always English (EN/NA/EU/AU) regardless of the card's
|
||||
// stored Language. Localized scans are intentionally not queried so the user
|
||||
// sees a consistent, well-stocked gallery (EN scans are the most complete).
|
||||
@@ -32,6 +38,12 @@ class YuGiOhCardPreviewSource final : public ICardPreviewSource {
|
||||
public:
|
||||
explicit YuGiOhCardPreviewSource(IHttpClient& http);
|
||||
|
||||
// Optional offline catalog for set+setNo → name reverse lookup. When null
|
||||
// or empty, detectVariantsBySetNo returns a clear "Update Sets" error.
|
||||
void setCatalogService(YuGiOhSetCatalogService* catalogStore) noexcept {
|
||||
catalogStore_ = catalogStore;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool supportsAutoDetectPrint() const noexcept override { return true; }
|
||||
|
||||
Result<std::string, PreviewLookupError>
|
||||
@@ -43,6 +55,12 @@ public:
|
||||
Result<std::vector<AutoDetectedPrint>> detectPrintVariants(std::string_view name,
|
||||
std::string_view setId) override;
|
||||
|
||||
Result<AutoDetectedPrint> detectBySetNo(std::string_view setId,
|
||||
std::string_view setNo) override;
|
||||
Result<std::vector<AutoDetectedPrint>> detectVariantsBySetNo(
|
||||
std::string_view setId,
|
||||
std::string_view setNo) override;
|
||||
|
||||
// ---- Yugipedia helpers (image preview path) ----------------------------
|
||||
|
||||
// Build the list of candidate Yugipedia file names to try, in priority
|
||||
@@ -116,8 +134,29 @@ public:
|
||||
std::string_view preferredSetName,
|
||||
std::string_view wantedCardName);
|
||||
|
||||
// Offline reverse lookup against a set-completion catalog. `setId` is the
|
||||
// pack's set code (e.g. "LOB"); `setNo` may be digits ("005") or a full
|
||||
// collector code ("LOB-005" / "LOB-EN005").
|
||||
static Result<std::vector<AutoDetectedPrint>>
|
||||
detectVariantsBySetNoFromCatalog(const YuGiOhSetCatalog& catalog,
|
||||
std::string_view setId,
|
||||
std::string_view setNo);
|
||||
|
||||
// YGOPRODeck cardset= dump filtered by collector digits (HTTP fallback when
|
||||
// the offline catalog is missing or has no match).
|
||||
static Result<std::vector<AutoDetectedPrint>>
|
||||
detectVariantsBySetNoFromCardset(const std::string& body,
|
||||
std::string_view preferredSetName,
|
||||
std::string_view setNo);
|
||||
|
||||
static std::string buildCardsetOnlyUrl(std::string_view setName);
|
||||
|
||||
private:
|
||||
IHttpClient& http_;
|
||||
IHttpClient& http_;
|
||||
YuGiOhSetCatalogService* catalogStore_{nullptr};
|
||||
// Cached offline catalog so reverse auto-detect does not re-parse a
|
||||
// multi-MB JSON file on every button click.
|
||||
mutable std::optional<YuGiOhSetCatalog> catalogCache_;
|
||||
};
|
||||
|
||||
} // namespace ccm
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "ccm/games/IGameModule.hpp"
|
||||
#include "ccm/games/yugioh/YuGiOhCardPreviewSource.hpp"
|
||||
#include "ccm/games/yugioh/YuGiOhSetSource.hpp"
|
||||
#include "ccm/services/YuGiOhSetCatalogService.hpp"
|
||||
|
||||
namespace ccm {
|
||||
|
||||
@@ -17,6 +18,15 @@ public:
|
||||
ISetSource& setSource() override { return setSource_; }
|
||||
ICardPreviewSource* cardPreviewSource() noexcept override { return &previewSource_; }
|
||||
|
||||
// Wire offline set catalog for set+setNo → name reverse auto-detect.
|
||||
void setCatalogService(YuGiOhSetCatalogService* catalogStore) noexcept {
|
||||
previewSource_.setCatalogService(catalogStore);
|
||||
}
|
||||
|
||||
[[nodiscard]] YuGiOhCardPreviewSource& previewSource() noexcept {
|
||||
return previewSource_;
|
||||
}
|
||||
|
||||
private:
|
||||
YuGiOhSetSource setSource_;
|
||||
YuGiOhCardPreviewSource previewSource_;
|
||||
|
||||
@@ -29,9 +29,11 @@ public:
|
||||
Result<std::vector<AutoDetectedPrint>> detectPrintVariants(std::string_view name,
|
||||
std::string_view setId) override;
|
||||
|
||||
Result<AutoDetectedPrint> detectBySetNo(std::string_view setNo) override;
|
||||
Result<AutoDetectedPrint> detectBySetNo(std::string_view setId,
|
||||
std::string_view setNo) override;
|
||||
|
||||
Result<std::vector<AutoDetectedPrint>> detectVariantsBySetNo(
|
||||
std::string_view setId,
|
||||
std::string_view setNo) override;
|
||||
|
||||
// Prefer "<Name> (Bandai)" / English / Sealdass page depending on setId.
|
||||
@@ -58,7 +60,9 @@ public:
|
||||
parsePageImagesResponse(const std::string& body);
|
||||
|
||||
static Result<std::vector<AutoDetectedPrint>>
|
||||
parseAskResponse(const std::string& body, std::string_view preferredSetId);
|
||||
parseAskResponse(const std::string& body,
|
||||
std::string_view preferredSetId,
|
||||
std::string_view wantedSetNo = {});
|
||||
|
||||
static AutoDetectedPrint enrichPrint(AutoDetectedPrint print,
|
||||
std::string_view pageTitle);
|
||||
@@ -69,7 +73,8 @@ private:
|
||||
Result<std::vector<AutoDetectedPrint>> askByName(std::string_view name,
|
||||
std::string_view setId);
|
||||
|
||||
Result<std::vector<AutoDetectedPrint>> askByNumber(std::string_view setNo);
|
||||
Result<std::vector<AutoDetectedPrint>> askByNumber(std::string_view setId,
|
||||
std::string_view setNo);
|
||||
|
||||
IHttpClient& http_;
|
||||
};
|
||||
|
||||
@@ -86,14 +86,19 @@ public:
|
||||
"Print variant listing not supported by this game.");
|
||||
}
|
||||
|
||||
// Optional lookup by collector / Bandai number (fills name + set + rarity).
|
||||
virtual Result<AutoDetectedPrint> detectBySetNo(std::string_view /*setNo*/) {
|
||||
// Optional lookup by set + collector / Bandai number (fills name + rarity).
|
||||
// `setId` uses the same meaning as detectPrintVariants for the game
|
||||
// (set id for Pokémon/Bandai; set display name for Digi-Battle; set code
|
||||
// id for Yu-Gi-Oh! catalog reverse lookup).
|
||||
virtual Result<AutoDetectedPrint> detectBySetNo(std::string_view /*setId*/,
|
||||
std::string_view /*setNo*/) {
|
||||
return Result<AutoDetectedPrint>::err(
|
||||
"Detect-by-number not supported by this game.");
|
||||
}
|
||||
|
||||
virtual Result<std::vector<AutoDetectedPrint>>
|
||||
detectVariantsBySetNo(std::string_view /*setNo*/) {
|
||||
detectVariantsBySetNo(std::string_view /*setId*/,
|
||||
std::string_view /*setNo*/) {
|
||||
return Result<std::vector<AutoDetectedPrint>>::err(
|
||||
"Detect-by-number variants not supported by this game.");
|
||||
}
|
||||
|
||||
@@ -81,10 +81,14 @@ public:
|
||||
std::string_view name,
|
||||
std::string_view setId);
|
||||
|
||||
Result<AutoDetectedPrint> detectBySetNo(Game game, std::string_view setNo);
|
||||
Result<AutoDetectedPrint> detectBySetNo(Game game,
|
||||
std::string_view setId,
|
||||
std::string_view setNo);
|
||||
|
||||
Result<std::vector<AutoDetectedPrint>> detectVariantsBySetNo(Game game,
|
||||
std::string_view setNo);
|
||||
Result<std::vector<AutoDetectedPrint>> detectVariantsBySetNo(
|
||||
Game game,
|
||||
std::string_view setId,
|
||||
std::string_view setNo);
|
||||
|
||||
// Download image bytes from a fully-qualified URL without going through
|
||||
// per-game preview-source resolution. Cached by URL (same LRU bound).
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
// Shared rule for bidirectional Set # Auto detect (name ↔ set number):
|
||||
// when both fields are filled, the field the user last edited is the lookup key.
|
||||
|
||||
namespace ccm {
|
||||
|
||||
enum class CardLookupEditField { None, Name, SetNo };
|
||||
|
||||
// Returns true when Auto detect should run setNo → name (reverse).
|
||||
// `nameEmpty` / `setNoEmpty` are already trimmed/normalized by the caller.
|
||||
// When both are empty the result is false (caller shows a validation message).
|
||||
// When only one is filled, that direction wins. When both are filled, SetNo
|
||||
// wins only if it was the last edited lookup field; otherwise Name wins
|
||||
// (including `None`, matching the historical default).
|
||||
[[nodiscard]] inline bool preferDetectBySetNo(bool nameEmpty,
|
||||
bool setNoEmpty,
|
||||
CardLookupEditField lastEdited) noexcept {
|
||||
if (nameEmpty) return !setNoEmpty;
|
||||
if (setNoEmpty) return false;
|
||||
return lastEdited == CardLookupEditField::SetNo;
|
||||
}
|
||||
|
||||
} // namespace ccm
|
||||
@@ -48,12 +48,39 @@ namespace ccm {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Digits from a full set code (after '-') or from a digits-only Set # field.
|
||||
[[nodiscard]] inline std::string ygoCollectorDigitsFromInput(std::string_view raw) {
|
||||
const std::string_view s = trimAsciiSpaces(raw);
|
||||
if (s.find('-') != std::string_view::npos) return ygoCollectorDigitsOnly(s);
|
||||
std::string out;
|
||||
out.reserve(s.size());
|
||||
for (unsigned char c : s) {
|
||||
if (std::isdigit(c) != 0) out.push_back(static_cast<char>(c));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string ygoDigitsStripLeadingZeros(std::string digits) {
|
||||
std::size_t i = 0;
|
||||
while (i + 1 < digits.size() && digits[i] == '0') ++i;
|
||||
if (i > 0) digits.erase(0, i);
|
||||
return digits;
|
||||
}
|
||||
|
||||
// True when both designate the same collector number, ignoring leading zeros
|
||||
// ("5" == "005") and accepting either a full set code or digits-only input.
|
||||
[[nodiscard]] inline bool ygoCollectorDigitsEqual(std::string_view a,
|
||||
std::string_view b) {
|
||||
return ygoDigitsStripLeadingZeros(ygoCollectorDigitsFromInput(a)) ==
|
||||
ygoDigitsStripLeadingZeros(ygoCollectorDigitsFromInput(b));
|
||||
}
|
||||
|
||||
// True when both strings designate the same printed slot: same abbreviation
|
||||
// before the first '-' (ASCII case-insensitive) and the same ordered digit run
|
||||
// extracted from everything after that dash.
|
||||
[[nodiscard]] inline bool ygoPrintingSlotsMatch(std::string_view a, std::string_view b) {
|
||||
if (ygoAbbrevBeforeDash(a) != ygoAbbrevBeforeDash(b)) return false;
|
||||
return ygoCollectorDigitsOnly(a) == ygoCollectorDigitsOnly(b);
|
||||
return ygoCollectorDigitsEqual(a, b);
|
||||
}
|
||||
|
||||
// YGOPRODeck sometimes lists European alternate numbering alongside NA prints under
|
||||
|
||||
Reference in New Issue
Block a user