Add workaround for pet lookups with leading digits
This was Dice's idea ty!! Now, instead of crashing when looking up any pet whose name starts with a number, we do a clever lil workaround instead! We don't get *all* the data back (we're missing metadata), but that's fine for the main use case of typing your pet name in at the homepage.
This commit is contained in:
parent
80428e9834
commit
756ee8dcb2
2 changed files with 74 additions and 19 deletions
|
@ -6,20 +6,38 @@ async function neopetsAmfphpCall(methodName, args) {
|
||||||
encodeURIComponent(methodName) +
|
encodeURIComponent(methodName) +
|
||||||
"/" +
|
"/" +
|
||||||
args.map(encodeURIComponent).join("/");
|
args.map(encodeURIComponent).join("/");
|
||||||
return await fetch(url).then((res) => res.json());
|
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`[AMFPHP] HTTP request failed, got status ${res.status} ${res.statusText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadPetMetaData(petName) {
|
export async function loadPetMetaData(petName) {
|
||||||
const response = await neopetsAmfphpCall("PetService.getPet", [petName]);
|
return neopetsAmfphpCall("PetService.getPet", [petName]);
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadCustomPetData(petName) {
|
export async function loadCustomPetData(petName) {
|
||||||
|
// HACK: The json.php amfphp endpoint is known not to support string
|
||||||
|
// arguments with leading digits. (It aggressively parses them as ints lmao.)
|
||||||
|
// So, we work around it by converting the pet name to its image hash, then
|
||||||
|
// prepending "@", which is a special code that can *also* be used in the
|
||||||
|
// CustomPetService in place of name, to get a pet's appearance from its image
|
||||||
|
// hash.
|
||||||
|
if (petName.match(/^[0-9]/)) {
|
||||||
|
const imageHash = await loadImageHashFromPetName(petName);
|
||||||
|
console.debug(
|
||||||
|
`[loadCustomPetData] Converted pet name ${petName} to @${imageHash}`
|
||||||
|
);
|
||||||
|
petName = "@" + imageHash;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await neopetsAmfphpCall("CustomPetService.getViewerData", [
|
return neopetsAmfphpCall("CustomPetService.getViewerData", [petName]);
|
||||||
petName,
|
|
||||||
]);
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If Neopets.com fails to find valid customization data, we return null.
|
// If Neopets.com fails to find valid customization data, we return null.
|
||||||
if (
|
if (
|
||||||
|
@ -33,6 +51,30 @@ export async function loadCustomPetData(petName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PETS_CP_URL_PATTERN = /https?:\/\/pets\.neopets\.com\/cp\/([a-z0-9]+)\/[0-9]+\/[0-9]+\.png/;
|
||||||
|
async function loadImageHashFromPetName(petName) {
|
||||||
|
const res = await fetch(`https://pets.neopets.com/cpn/${petName}/1/1.png`, {
|
||||||
|
redirect: "manual",
|
||||||
|
});
|
||||||
|
if (res.status !== 302) {
|
||||||
|
throw new Error(
|
||||||
|
`[loadImageHashFromPetName] expected /cpn/ URL to redirect with status ` +
|
||||||
|
`302, but instead got status ${res.status} ${res.statusText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUrl = res.headers.get("location");
|
||||||
|
const newUrlMatch = newUrl.match(PETS_CP_URL_PATTERN);
|
||||||
|
if (newUrlMatch == null) {
|
||||||
|
throw new Error(
|
||||||
|
`[loadImageHashFromPetName] expected /cpn/ URL to redirect to a /cp/ ` +
|
||||||
|
`URL matching ${PETS_CP_URL_PATTERN}, but got ${newUrl}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUrlMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadNCMallPreviewImageHash(basicImageHash, itemIds) {
|
export async function loadNCMallPreviewImageHash(basicImageHash, itemIds) {
|
||||||
const query = new URLSearchParams();
|
const query = new URLSearchParams();
|
||||||
query.append("selPetsci", basicImageHash);
|
query.append("selPetsci", basicImageHash);
|
||||||
|
|
|
@ -57,16 +57,22 @@ const resolvers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, look for a pet state matching the same pose. (This can happen if
|
// Next, look for a pet state matching the same pose. (This can happen if
|
||||||
// modeling data for this pet hasn't saved yet.)
|
// modeling data for this pet hasn't saved yet.) (We might skip this step
|
||||||
const pose = getPoseFromPetData(petMetaData, customPetData);
|
// if we couldn't load either the custom pet data or metadata, which is
|
||||||
petState = petStates.find((ps) => getPoseFromPetState(ps) === pose);
|
// expected if e.g. the pet name starts with a leading digit, so we can
|
||||||
if (petState) {
|
// use a workaround for the custom pet data but the JSON endpoint for
|
||||||
console.warn(
|
// metadata fails.)
|
||||||
`Warning: For pet "${name}", fell back to pet state ${petState.id} ` +
|
if (petMetaData != null && customPetData != null) {
|
||||||
`because it matches pose ${pose}. Actual pet state for these ` +
|
const pose = getPoseFromPetData(petMetaData, customPetData);
|
||||||
`assets not found: ${swfAssetIdsString}`
|
petState = petStates.find((ps) => getPoseFromPetState(ps) === pose);
|
||||||
);
|
if (petState) {
|
||||||
return { id: petState.id };
|
console.warn(
|
||||||
|
`Warning: For pet "${name}", fell back to pet state ${petState.id} ` +
|
||||||
|
`because it matches pose ${pose}. Actual pet state for these ` +
|
||||||
|
`assets not found: ${swfAssetIdsString}`
|
||||||
|
);
|
||||||
|
return { id: petState.id };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, look for an UNKNOWN pet state. (This can happen if modeling
|
// Finally, look for an UNKNOWN pet state. (This can happen if modeling
|
||||||
|
@ -119,10 +125,17 @@ const resolvers = {
|
||||||
) => {
|
) => {
|
||||||
const [customPetData, petMetaData] = await Promise.all([
|
const [customPetData, petMetaData] = await Promise.all([
|
||||||
loadCustomPetData(petName),
|
loadCustomPetData(petName),
|
||||||
loadPetMetaData(petName),
|
loadPetMetaData(petName).catch((error) => {
|
||||||
|
console.warn(`Couldn't load metadata for pet ${petName}: `, error);
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (customPetData != null && process.env["USE_NEW_MODELING"] === "1") {
|
if (
|
||||||
|
customPetData != null &&
|
||||||
|
petMetaData != null &&
|
||||||
|
process.env["USE_NEW_MODELING"] === "1"
|
||||||
|
) {
|
||||||
await saveModelingData(customPetData, petMetaData, {
|
await saveModelingData(customPetData, petMetaData, {
|
||||||
db,
|
db,
|
||||||
petTypeBySpeciesAndColorLoader,
|
petTypeBySpeciesAndColorLoader,
|
||||||
|
|
Loading…
Reference in a new issue