diff --git a/package.json b/package.json index 9b67710..597b732 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dependencies": { "glob-parent": "^6.0.0", "got-cjs": "^12.5.4", + "htmlparser2": "^9.1.0", "linqts": "^1.14.4" }, "scripts": { diff --git a/src/imageHelper.ts b/src/imageHelper.ts new file mode 100644 index 0000000..793b5ce --- /dev/null +++ b/src/imageHelper.ts @@ -0,0 +1,19 @@ +import fetch from "got-cjs"; +import * as htmlparser from "htmlparser2"; + +export default class ImageHelper { + public static async FetchImageFromRedditGallery(url: string): Promise { + const fetched = await fetch(url); + + if (!fetched) { + return undefined; + } + + const dom = htmlparser.parseDocument(fetched.body); + const img = htmlparser.DomUtils.findOne((x => x.tagName == "img" && x.attributes.find(y => y.value.includes("https://preview.redd.it")) != null), dom.children, true); + + const imgSrc = img?.attributes.find(x => x.name == "src")?.value; + + return imgSrc; + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 400567e..8f85175 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import IRedditResult from "./contracts/IRedditResult.js"; import fetch from "got-cjs"; import { List } from 'linqts'; import IFetchResult from "./contracts/IFetchResult.js"; +import ImageHelper from "./imageHelper"; const sortable = [ 'new', @@ -13,7 +14,7 @@ const sortable = [ export default async function randomBunny(subreddit: string, sortBy?: string): Promise { if (!sortBy || !sortable.includes(sortBy)) sortBy = 'hot'; - const result = await fetch(`https://reddit.com/r/${subreddit}/${sortBy}.json`); + const result = await fetch(`https://reddit.com/r/${subreddit}/${sortBy}.json?limit=100`); if (!result) { return { @@ -32,7 +33,7 @@ export default async function randomBunny(subreddit: string, sortBy?: string): P const data: IFetchResult[] = json.data.children; const dataWithImages = new List(data) - .Where(x => x!.data.url.includes('.jpg') || x!.data.url.includes('.png')) + .Where(x => x!.data.url.includes('.jpg') || x!.data.url.includes('.png') || x!.data.url.includes("/gallery/")) .ToArray(); let random = 0; @@ -49,6 +50,22 @@ export default async function randomBunny(subreddit: string, sortBy?: string): P const randomData = randomSelect.data; + let url: string; + + if (randomData.url.includes("/gallery")) { + const galleryImage = await ImageHelper.FetchImageFromRedditGallery(randomData.url); + + if (!galleryImage) { + return { + IsSuccess: false, + } + } + + url = galleryImage; + } else { + url = randomData.url; + } + const redditResult: IRedditResult = { Archived: randomData['archived'], Downs: randomData['downs'], @@ -58,7 +75,7 @@ export default async function randomBunny(subreddit: string, sortBy?: string): P SubredditSubscribers: randomData['subreddit_subscribers'], Title: randomData['title'], Ups: randomData['ups'], - Url: randomData['url'] + Url: url, }; return { diff --git a/tests/imageHelper.test.ts b/tests/imageHelper.test.ts new file mode 100644 index 0000000..556264c --- /dev/null +++ b/tests/imageHelper.test.ts @@ -0,0 +1,9 @@ +describe("FetchImageFromRedditGallery", () => { + test.todo("EXPECT image url to be returned"); + + test.todo("GIVEN fetch is unable to return data, EXPECT undefined returned"); + + test.todo("GIVEN image tag is not found, EXPECT undefined returned"); + + test.todo("GIVEN image source attribute is not found, EXPECT undefined returned"); +}); \ No newline at end of file diff --git a/tests/index.test.ts b/tests/index.test.ts index 6eafc35..87efd7a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -33,7 +33,7 @@ describe('randomBunny', () => { expect(result.IsSuccess).toBeTruthy(); expect(result.Result).toBeDefined(); - expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json'); + expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json?limit=100'); }); test('GIVEN sortBy is NOT supplied, expect it to default to hot', async () => { @@ -64,7 +64,7 @@ describe('randomBunny', () => { expect(result.IsSuccess).toBeTruthy(); expect(result.Result).toBeDefined(); - expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/hot.json'); + expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/hot.json?limit=100'); }); test('GIVEN sortBy is NOT valid, expect it to default to hot', async () => { @@ -95,7 +95,7 @@ describe('randomBunny', () => { expect(result.IsSuccess).toBeTruthy(); expect(result.Result).toBeDefined(); - expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/hot.json'); + expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/hot.json?limit=100'); }); test('GIVEN the fetch fails, EXPECT failure result', async () => { @@ -106,7 +106,7 @@ describe('randomBunny', () => { expect(result.IsSuccess).toBeFalsy(); expect(result.Result).toBeUndefined(); - expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json'); + expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json?limit=100'); }); test('GIVEN the result is NOT valid JSON, EXPECT failure result', async () => { @@ -119,7 +119,7 @@ describe('randomBunny', () => { expect(result.IsSuccess).toBeFalsy(); expect(result.Result).toBeUndefined(); - expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json'); + expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json?limit=100'); }); test('GIVEN randomSelect does NOT find a response, EXPECT failure result', async () => { @@ -136,7 +136,7 @@ describe('randomBunny', () => { expect(result.IsSuccess).toBeFalsy(); expect(result.Result).toBeUndefined(); - expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json'); + expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json?limit=100'); }); test('GIVEN randomSelect does NOT find a valid response, EXPECT failure result', async () => { @@ -167,6 +167,12 @@ describe('randomBunny', () => { expect(result.IsSuccess).toBeFalsy(); expect(result.Result).toBeUndefined(); - expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json'); + expect(fetchMock).toBeCalledWith('https://reddit.com/r/rabbits/new.json?limit=100'); }); + + test.todo("GIVEN data fetched is a gallery AND an image is returned from the helper, EXPECT this to be used"); + + test.todo("GIVEN data fetched is a gallery AND an image is not returned from the helper, EXPECT error"); + + test.todo("GIVEN data fetched is not a gallery, EXPECT url to be used directly"); }); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 1414a90..cc0a27a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1716,6 +1716,36 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -1757,6 +1787,11 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +entities@^4.2.0, entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2286,6 +2321,16 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +htmlparser2@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" + integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.1.0" + entities "^4.5.0" + http-cache-semantics@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"