diff --git a/src/adapter/genericJson.ts b/src/adapter/genericJson.ts index fbaaff28..6a3a7920 100644 --- a/src/adapter/genericJson.ts +++ b/src/adapter/genericJson.ts @@ -44,11 +44,11 @@ class GenericJsonAdapter extends BaseAdapter { const authorUrlJSONPath = this._settings.getString('author-url-path'); for (let i = 0; i < 5 && wallpaperResult.length < count; i++) { - const returnObject = JSONPath.getTarget(response_body, imageJSONPath); - if (!returnObject || (typeof returnObject.Object !== 'string' && typeof returnObject.Object !== 'number') || returnObject.Object === '') + const [returnObject, resolvedPath] = JSONPath.getTarget(response_body, imageJSONPath); + if (!returnObject || (typeof returnObject !== 'string' && typeof returnObject !== 'number') || returnObject === '') throw new Error('Unexpected json member found'); - const imageDownloadUrl = this._settings.getString('image-prefix') + String(returnObject.Object); + const imageDownloadUrl = this._settings.getString('image-prefix') + String(returnObject); const imageBlocked = this._isImageBlocked(Utils.fileName(imageDownloadUrl)); // Don't retry without @random present in JSONPath @@ -60,32 +60,23 @@ class GenericJsonAdapter extends BaseAdapter { if (imageBlocked) continue; - // '@random' would yield different results so lets make sure the values stay - // the same as long as the path is identical - const samePath = imageJSONPath.substring(0, Utils.findFirstDifference(imageJSONPath, postJSONPath)); - - // count occurrences of '@random' to slice the array later - // https://stackoverflow.com/a/4009768 - const occurrences = (samePath.match(/@random/g) || []).length; - const slicedRandomNumbers = returnObject?.RandomNumbers?.slice(0, occurrences); - // A bit cumbersome to handle "unknown" in the following parts: // https://github.com/microsoft/TypeScript/issues/27706 let postUrl: string; - const postUrlObject = JSONPath.getTarget(response_body, postJSONPath, slicedRandomNumbers ? [...slicedRandomNumbers] : undefined, false)?.Object; + const postUrlObject = JSONPath.getTarget(response_body, JSONPath.replaceRandomInPath(postJSONPath, resolvedPath))[0]; if (typeof postUrlObject === 'string' || typeof postUrlObject === 'number') postUrl = this._settings.getString('post-prefix') + String(postUrlObject); else postUrl = ''; let authorName: string | null = null; - const authorNameObject = JSONPath.getTarget(response_body, authorNameJSONPath, slicedRandomNumbers ? [...slicedRandomNumbers] : undefined, false)?.Object; + const authorNameObject = JSONPath.getTarget(response_body, JSONPath.replaceRandomInPath(authorNameJSONPath, resolvedPath))[0]; if (typeof authorNameObject === 'string' && authorNameObject !== '') authorName = authorNameObject; let authorUrl: string; - const authorUrlObject = JSONPath.getTarget(response_body, authorUrlJSONPath, slicedRandomNumbers ? [...slicedRandomNumbers] : undefined, false)?.Object; + const authorUrlObject = JSONPath.getTarget(response_body, JSONPath.replaceRandomInPath(authorUrlJSONPath, resolvedPath))[0]; if (typeof authorUrlObject === 'string' || typeof authorUrlObject === 'number') authorUrl = this._settings.getString('author-url-prefix') + String(authorUrlObject); else diff --git a/src/jsonPath.ts b/src/jsonPath.ts index 3c2b9a85..648a122d 100644 --- a/src/jsonPath.ts +++ b/src/jsonPath.ts @@ -8,24 +8,13 @@ import * as Utils from './utils.js'; * * @param {unknown} inputObject A JSON object * @param {string} inputString JSONPath to follow, see wiki for syntax - * @param {number[]} randomNumbers Array of pre-generated numbers - * @param {boolean} newRandomness Whether to ignore a given randomNumbers array */ -function getTarget(inputObject: unknown, inputString: string, randomNumbers?: number[], newRandomness?: boolean): { Object: unknown, RandomNumbers?: number[] } | null { +function getTarget(inputObject: unknown, inputString: string): [object: unknown, chosenPath: string] { if (!inputObject) - return null; + return [null, '']; - if (inputString.length === 0) { - return { - Object: inputObject, - RandomNumbers: randomNumbers, - }; - } - - if (!randomNumbers) { - randomNumbers = []; - newRandomness = true; - } + if (inputString.length === 0) + return [inputObject, inputString]; let startDot = inputString.indexOf('.'); if (startDot === -1) @@ -40,9 +29,10 @@ function getTarget(inputObject: unknown, inputString: string, randomNumbers?: nu // Expect Object here const targetObject = _getObjectMember(inputObject, keyString); if (!targetObject) - return null; + return [null, '']; - return getTarget(targetObject, inputStringTail, randomNumbers, newRandomness); + const [object, path] = getTarget(targetObject, inputStringTail); + return [object, inputString.slice(0, inputString.length - inputStringTail.length) + path]; } else { const indexString = keyString.slice(startParentheses + 1, keyString.length - 1); keyString = keyString.slice(0, startParentheses); @@ -50,30 +40,20 @@ function getTarget(inputObject: unknown, inputString: string, randomNumbers?: nu // Expect an Array at this point const targetObject = _getObjectMember(inputObject, keyString); if (!targetObject || !Array.isArray(targetObject)) - return null; + return [null, '']; switch (indexString) { case '@random': { - let randomNumber: number = -1; - let randomElement: unknown = null; - - if (!newRandomness && randomNumbers.length > 0) { - // Take and remove first element - randomNumber = randomNumbers.shift() ?? -1; - randomElement = targetObject[randomNumber]; - } else { - [randomElement, randomNumber] = _randomElement(targetObject); - - if (newRandomness) - randomNumbers.push(randomNumber); - } - - return getTarget(randomElement, inputStringTail, randomNumbers, newRandomness); + const [chosenElement, chosenNumber] = _randomElement(targetObject); + const [object, path] = getTarget(chosenElement, inputStringTail); + return [object, inputString.slice(0, inputString.length - inputStringTail.length).replace('@random', String(chosenNumber)) + path]; } // add special keywords here - default: + default: { // expecting integer - return getTarget(targetObject[parseInt(indexString)], inputStringTail, randomNumbers, newRandomness); + const [object, path] = getTarget(targetObject[parseInt(indexString)], inputStringTail); + return [object, inputString.slice(0, inputString.length - inputStringTail.length) + path]; + } } } } @@ -106,4 +86,32 @@ function _randomElement(array: Array): [T, number] { return [array[randomNumber], randomNumber]; } -export {getTarget}; +/** + * Replace '@random' according to an already resolved path. + * + * '@random' would yield different results so this makes sure the values stay + * the same as long as the path is identical. + * + * @param {string} randomPath Path containing '@random' to resolve + * @param {string} resolvedPath Path with resolved '@random' + */ +function replaceRandomInPath(randomPath: string, resolvedPath: string): string { + if (!randomPath.includes('@random')) + return randomPath; + + let newPath = randomPath; + while (newPath.includes('@random')) { + const startRandom = newPath.indexOf('@random'); + + // abort if path is not equal up to this point + if (newPath.substring(0, startRandom) !== resolvedPath.substring(0, startRandom)) + break; + + const endParenthesis = resolvedPath.indexOf(']', startRandom); + newPath = newPath.replace('@random', resolvedPath.substring(startRandom, endParenthesis)); + } + + return newPath; +} + +export {getTarget, replaceRandomInPath};