mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-09-04 08:13:17 +00:00
patch: Feature/pkm autodetect (#15)
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ccm {
|
||||
|
||||
@@ -23,6 +25,19 @@ std::string normalizeNumber(std::string_view setNo) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string trim(std::string s) {
|
||||
while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front()))) s.erase(s.begin());
|
||||
while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back()))) s.pop_back();
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string toLower(std::string s) {
|
||||
for (char& ch : s) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PokemonCardPreviewSource::PokemonCardPreviewSource(IHttpClient& http) : http_(http) {}
|
||||
@@ -48,6 +63,14 @@ std::string PokemonCardPreviewSource::buildSearchUrl(std::string_view name,
|
||||
rfc3986PercentEncode(query);
|
||||
}
|
||||
|
||||
std::string PokemonCardPreviewSource::buildDetectSearchUrl(std::string_view name,
|
||||
std::string_view setId) {
|
||||
std::string url = buildSearchUrl(name, setId, "");
|
||||
url += "&select=name,number,rarity,set";
|
||||
url += "&pageSize=50";
|
||||
return url;
|
||||
}
|
||||
|
||||
Result<std::string, PreviewLookupError>
|
||||
PokemonCardPreviewSource::parseResponse(const std::string& body) {
|
||||
using R = Result<std::string, PreviewLookupError>;
|
||||
@@ -91,4 +114,87 @@ PokemonCardPreviewSource::fetchImageUrl(std::string_view name,
|
||||
return parseResponse(resp.value());
|
||||
}
|
||||
|
||||
Result<std::vector<AutoDetectedPrint>> PokemonCardPreviewSource::parsePrintVariants(
|
||||
const std::string& body,
|
||||
std::string_view setId,
|
||||
std::string_view wantedCardName) {
|
||||
using R = Result<std::vector<AutoDetectedPrint>>;
|
||||
try {
|
||||
const auto j = nlohmann::json::parse(body);
|
||||
if (!j.contains("data") || !j.at("data").is_array() || j.at("data").empty()) {
|
||||
return R::err("Pokemon TCG returned no matching cards.");
|
||||
}
|
||||
const std::string wantedSetId = trim(std::string(setId));
|
||||
const std::string wantedNameLower = toLower(trim(std::string(wantedCardName)));
|
||||
|
||||
std::vector<AutoDetectedPrint> collected;
|
||||
auto pushCard = [&collected](const nlohmann::json& card) {
|
||||
AutoDetectedPrint out;
|
||||
out.setNo = trim(card.value("number", ""));
|
||||
out.rarity = trim(card.value("rarity", ""));
|
||||
if (out.setNo.empty() && out.rarity.empty()) return;
|
||||
collected.push_back(std::move(out));
|
||||
};
|
||||
|
||||
for (const auto& card : j.at("data")) {
|
||||
if (!wantedNameLower.empty()) {
|
||||
const std::string cardName = trim(card.value("name", ""));
|
||||
if (toLower(cardName) != wantedNameLower) continue;
|
||||
}
|
||||
if (!wantedSetId.empty()) {
|
||||
std::string cardSetId;
|
||||
if (card.contains("set") && card.at("set").is_object()) {
|
||||
cardSetId = trim(card.at("set").value("id", ""));
|
||||
}
|
||||
if (cardSetId != wantedSetId) continue;
|
||||
}
|
||||
pushCard(card);
|
||||
}
|
||||
|
||||
if (collected.empty()) {
|
||||
if (!wantedNameLower.empty() && !wantedSetId.empty()) {
|
||||
return R::err("Could not auto-detect set print metadata.");
|
||||
}
|
||||
return R::err("Pokemon TCG returned no matching cards.");
|
||||
}
|
||||
|
||||
std::vector<AutoDetectedPrint> deduped;
|
||||
deduped.reserve(collected.size());
|
||||
std::unordered_set<std::string> seen;
|
||||
seen.reserve(collected.size() * 2);
|
||||
for (auto& p : collected) {
|
||||
const std::string key = p.setNo + '\0' + p.rarity;
|
||||
if (seen.insert(key).second) deduped.push_back(std::move(p));
|
||||
}
|
||||
return R::ok(std::move(deduped));
|
||||
} catch (const std::exception& e) {
|
||||
return R::err(std::string("Pokemon TCG JSON parse error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
Result<AutoDetectedPrint> PokemonCardPreviewSource::detectFirstPrint(std::string_view name,
|
||||
std::string_view setId) {
|
||||
auto list = detectPrintVariants(name, setId);
|
||||
if (!list || list.value().empty()) {
|
||||
if (!list) return Result<AutoDetectedPrint>::err(list.error());
|
||||
return Result<AutoDetectedPrint>::err("Could not auto-detect set print metadata.");
|
||||
}
|
||||
return Result<AutoDetectedPrint>::ok(list.value().front());
|
||||
}
|
||||
|
||||
Result<std::vector<AutoDetectedPrint>> PokemonCardPreviewSource::detectPrintVariants(
|
||||
std::string_view name,
|
||||
std::string_view setId) {
|
||||
using R = Result<std::vector<AutoDetectedPrint>>;
|
||||
const std::string url = buildDetectSearchUrl(name, setId);
|
||||
auto resp = http_.get(url);
|
||||
if (resp) {
|
||||
return parsePrintVariants(resp.value(), setId, name);
|
||||
}
|
||||
const std::string fallbackUrl = buildDetectSearchUrl(name, "");
|
||||
auto fallback = http_.get(fallbackUrl);
|
||||
if (!fallback) return R::err(fallback.error());
|
||||
return parsePrintVariants(fallback.value(), setId, name);
|
||||
}
|
||||
|
||||
} // namespace ccm
|
||||
|
||||
Reference in New Issue
Block a user