Add image helper class to handle reddit galleries
This commit is contained in:
parent
27e08e6f86
commit
00b51584b6
6 changed files with 107 additions and 10 deletions
|
@ -18,6 +18,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob-parent": "^6.0.0",
|
"glob-parent": "^6.0.0",
|
||||||
"got-cjs": "^12.5.4",
|
"got-cjs": "^12.5.4",
|
||||||
|
"htmlparser2": "^9.1.0",
|
||||||
"linqts": "^1.14.4"
|
"linqts": "^1.14.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
19
src/imageHelper.ts
Normal file
19
src/imageHelper.ts
Normal file
|
@ -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<string | undefined> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
23
src/index.ts
23
src/index.ts
|
@ -3,6 +3,7 @@ import IRedditResult from "./contracts/IRedditResult.js";
|
||||||
import fetch from "got-cjs";
|
import fetch from "got-cjs";
|
||||||
import { List } from 'linqts';
|
import { List } from 'linqts';
|
||||||
import IFetchResult from "./contracts/IFetchResult.js";
|
import IFetchResult from "./contracts/IFetchResult.js";
|
||||||
|
import ImageHelper from "./imageHelper";
|
||||||
|
|
||||||
const sortable = [
|
const sortable = [
|
||||||
'new',
|
'new',
|
||||||
|
@ -13,7 +14,7 @@ const sortable = [
|
||||||
export default async function randomBunny(subreddit: string, sortBy?: string): Promise<IReturnResult> {
|
export default async function randomBunny(subreddit: string, sortBy?: string): Promise<IReturnResult> {
|
||||||
if (!sortBy || !sortable.includes(sortBy)) sortBy = 'hot';
|
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) {
|
if (!result) {
|
||||||
return {
|
return {
|
||||||
|
@ -32,7 +33,7 @@ export default async function randomBunny(subreddit: string, sortBy?: string): P
|
||||||
const data: IFetchResult[] = json.data.children;
|
const data: IFetchResult[] = json.data.children;
|
||||||
|
|
||||||
const dataWithImages = new List<IFetchResult>(data)
|
const dataWithImages = new List<IFetchResult>(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();
|
.ToArray();
|
||||||
|
|
||||||
let random = 0;
|
let random = 0;
|
||||||
|
@ -49,6 +50,22 @@ export default async function randomBunny(subreddit: string, sortBy?: string): P
|
||||||
|
|
||||||
const randomData = randomSelect.data;
|
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 = {
|
const redditResult: IRedditResult = {
|
||||||
Archived: randomData['archived'],
|
Archived: randomData['archived'],
|
||||||
Downs: randomData['downs'],
|
Downs: randomData['downs'],
|
||||||
|
@ -58,7 +75,7 @@ export default async function randomBunny(subreddit: string, sortBy?: string): P
|
||||||
SubredditSubscribers: randomData['subreddit_subscribers'],
|
SubredditSubscribers: randomData['subreddit_subscribers'],
|
||||||
Title: randomData['title'],
|
Title: randomData['title'],
|
||||||
Ups: randomData['ups'],
|
Ups: randomData['ups'],
|
||||||
Url: randomData['url']
|
Url: url,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
9
tests/imageHelper.test.ts
Normal file
9
tests/imageHelper.test.ts
Normal file
|
@ -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");
|
||||||
|
});
|
|
@ -33,7 +33,7 @@ describe('randomBunny', () => {
|
||||||
expect(result.IsSuccess).toBeTruthy();
|
expect(result.IsSuccess).toBeTruthy();
|
||||||
expect(result.Result).toBeDefined();
|
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 () => {
|
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.IsSuccess).toBeTruthy();
|
||||||
expect(result.Result).toBeDefined();
|
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 () => {
|
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.IsSuccess).toBeTruthy();
|
||||||
expect(result.Result).toBeDefined();
|
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 () => {
|
test('GIVEN the fetch fails, EXPECT failure result', async () => {
|
||||||
|
@ -106,7 +106,7 @@ describe('randomBunny', () => {
|
||||||
expect(result.IsSuccess).toBeFalsy();
|
expect(result.IsSuccess).toBeFalsy();
|
||||||
expect(result.Result).toBeUndefined();
|
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 () => {
|
test('GIVEN the result is NOT valid JSON, EXPECT failure result', async () => {
|
||||||
|
@ -119,7 +119,7 @@ describe('randomBunny', () => {
|
||||||
expect(result.IsSuccess).toBeFalsy();
|
expect(result.IsSuccess).toBeFalsy();
|
||||||
expect(result.Result).toBeUndefined();
|
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 () => {
|
test('GIVEN randomSelect does NOT find a response, EXPECT failure result', async () => {
|
||||||
|
@ -136,7 +136,7 @@ describe('randomBunny', () => {
|
||||||
expect(result.IsSuccess).toBeFalsy();
|
expect(result.IsSuccess).toBeFalsy();
|
||||||
expect(result.Result).toBeUndefined();
|
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 () => {
|
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.IsSuccess).toBeFalsy();
|
||||||
expect(result.Result).toBeUndefined();
|
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");
|
||||||
});
|
});
|
45
yarn.lock
45
yarn.lock
|
@ -1716,6 +1716,36 @@ doctrine@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
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:
|
dot-prop@^5.2.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
|
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:
|
dependencies:
|
||||||
once "^1.4.0"
|
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:
|
error-ex@^1.3.1:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
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"
|
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||||
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
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:
|
http-cache-semantics@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
|
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
|
||||||
|
|
Loading…
Reference in a new issue