@ptrumpis/snap-lens-web-crawler
Version:
Crawl and download Snap Lenses from *lens.snapchat.com* with ease.
285 lines (248 loc) • 12.3 kB
JavaScript
import assert from 'assert';
import nock from 'nock';
import SnapLensWebCrawler from '../src/lib/crawler.js';
import { CrawlerFailure, CrawlerHTTPStatusFailure, CrawlerNotFoundFailure } from '../src/lib/failure.js';
function getMockPage(mockJsonData) {
const mockHtmlResponse = `
<html>
<head><title>Mock Page</title></head>
<body>
<script id="__NEXT_DATA__" type="application/json">
${JSON.stringify(mockJsonData)}
</script>
</body>
</html>
`;
return mockHtmlResponse;
}
describe('SnapLensWebCrawler', () => {
let crawler;
beforeEach(() => {
crawler = new SnapLensWebCrawler({ maxRequestRetries: 0, verbose: false });
});
afterEach(() => {
crawler.destroy();
nock.cleanAll();
});
it('should get lenses from a URL', async () => {
const mockJsonData = {
props: {
pageProps: {
lensDisplayInfo: [{ scannableUuid: 'example-hash1', lensId: '11234567890', lensName: 'Test Lens 1' }],
moreLenses: [{ scannableUuid: 'example-hash2', lensId: '21234567890', lensName: 'Test Lens 2' }],
lenses: [{ scannableUuid: 'example-hash3', lensId: '31234567890', lensName: 'Test Lens 3' }],
topLenses: [{ scannableUuid: 'example-hash4', lensId: '41234567890', lensName: 'Test Lens 4' }]
}
}
};
nock('https://example.com')
.get('/lenses')
.reply(200, getMockPage(mockJsonData), { 'Content-Type': 'text/html' });
const lenses = await crawler.getLensesFromUrl('https://example.com/lenses');
assert(Array.isArray(lenses), 'Result should be an array');
assert.strictEqual(lenses.length, 4, 'Should return 4 lenses');
});
it('should get a lens by hash', async () => {
const mockJsonData = {
props: {
pageProps: {
lensDisplayInfo: {
scannableUuid: 'example-hash',
lensId: '11234567890',
lensName: 'Test Lens'
}
}
}
};
nock('https://lens.snapchat.com')
.get('/example-hash')
.reply(200, getMockPage(mockJsonData), { 'Content-Type': 'text/html' });
const lens = await crawler.getLensByHash('example-hash');
assert(!(lens instanceof CrawlerFailure), 'Lens should not be an instance of CrawlerFailure');
assert(lens, 'Lens should be returned');
assert.strictEqual(lens.uuid, 'example-hash', 'Lens UUID should match');
assert.strictEqual(lens.unlockable_id, '11234567890', 'Lens ID should match');
assert.strictEqual(lens.lens_name, 'Test Lens', 'Lens name should match');
});
it('should get a lens by archived snapshot', async () => {
const mockWaybackResponse = {
archived_snapshots: {
closest: {
available: true,
url: 'https://web.archive.org/web/20220101000000/https://lens.snapchat.com/example-hash',
timestamp: '20220101000000',
status: '200'
}
}
};
const mockJsonData = {
props: {
pageProps: {
lensDisplayInfo: {
scannableUuid: 'example-hash',
lensId: '11234567890',
lensName: 'Test Lens',
lensResource: {
archiveLink: 'https://example.com/download/file.lns'
}
}
}
}
};
nock('https://archive.org')
.get('/wayback/available')
.query((query) => query.url === 'lens.snapchat.com/example-hash*')
.reply(200, mockWaybackResponse);
nock('https://web.archive.org')
.get('/web/20220101000000/https://lens.snapchat.com/example-hash')
.reply(200, getMockPage(mockJsonData), { 'Content-Type': 'text/html' });
const lens = await crawler.getLensByArchivedSnapshot('example-hash');
assert(!(lens instanceof CrawlerFailure), 'Lens should not be an instance of CrawlerFailure');
assert(lens, 'Lens should be returned');
assert.strictEqual(lens.uuid, 'example-hash', 'Lens UUID should match');
assert.strictEqual(lens.unlockable_id, '11234567890', 'Lens ID should match');
assert.strictEqual(lens.lens_name, 'Test Lens', 'Lens name should match');
});
it('should get more lenses by hash', async () => {
const mockJsonData = {
props: {
pageProps: {
moreLenses: [
{ scannableUuid: 'example-hash1', lensId: '11234567890', lensName: 'Test Lens 1' },
{ scannableUuid: 'example-hash2', lensId: '21234567890', lensName: 'Test Lens 2' },
{ scannableUuid: 'example-hash3', lensId: '31234567890', lensName: 'Test Lens 3' }
],
}
}
};
nock('https://lens.snapchat.com')
.get('/example-hash')
.reply(200, getMockPage(mockJsonData), { 'Content-Type': 'text/html' });
const lenses = await crawler.getMoreLensesByHash('example-hash');
assert(Array.isArray(lenses), 'Result should be an array');
assert.strictEqual(lenses.length, 3, 'Should return 3 lenses');
});
it('should get lenses by username', async () => {
const mockJsonData = {
props: {
pageProps: {
lenses: [
{ scannableUuid: 'example-hash1', lensId: '11234567890', lensName: 'Test Lens 1' },
{ scannableUuid: 'example-hash2', lensId: '21234567890', lensName: 'Test Lens 2' },
{ scannableUuid: 'example-hash3', lensId: '31234567890', lensName: 'Test Lens 3' }
],
}
}
};
nock('https://www.snapchat.com')
.get('/add/example-username')
.reply(200, getMockPage(mockJsonData), { 'Content-Type': 'text/html' });
const lenses = await crawler.getLensesByUsername('example-username');
assert(Array.isArray(lenses), 'Result should be an array');
assert.strictEqual(lenses.length, 3, 'Should return 3 lenses');
});
it('should get lenses by creator', async () => {
const mockJsonData = {
lensesList: [
{ lensId: '11234567890', name: 'Test Lens 1', creatorName: 'test', deeplinkUrl: 'https://www.snapchat.com/unlock/?type=SNAPCODE&uuid=example-hash&metadata=01' },
{ lensId: '21234567890', name: 'Test Lens 2', creatorName: 'test', deeplinkUrl: 'https://www.snapchat.com/unlock/?type=SNAPCODE&uuid=example-hash&metadata=01' },
{ lensId: '31234567890', name: 'Test Lens 3', creatorName: 'test', deeplinkUrl: 'https://www.snapchat.com/unlock/?type=SNAPCODE&uuid=example-hash&metadata=01' }
],
};
nock('https://lensstudio.snapchat.com')
.get('/v1/creator/lenses/')
.query((query) => query.slug === 'example-slug')
.reply(200, JSON.stringify(mockJsonData), { 'Content-Type': 'application/json' });
const lenses = await crawler.getLensesByCreator('example-slug');
assert(Array.isArray(lenses), 'Result should be an array');
assert.strictEqual(lenses.length, 3, 'Should return 3 lenses');
});
it('should get top lenses by category', async () => {
const mockJsonData = {
props: {
pageProps: {
topLenses: [
{ scannableUuid: 'example-hash1', lensId: '11234567890', lensName: 'Test Lens 1' },
{ scannableUuid: 'example-hash2', lensId: '21234567890', lensName: 'Test Lens 2' },
{ scannableUuid: 'example-hash3', lensId: '31234567890', lensName: 'Test Lens 3' }
],
}
}
};
nock('https://www.snapchat.com')
.get('/lens/')
.reply(200, getMockPage(mockJsonData), { 'Content-Type': 'text/html' });
const lenses = await crawler.getTopLensesByCategory('default');
assert(Array.isArray(lenses), 'Result should be an array');
});
it('should search lenses', async () => {
const mockJsonData = {
props: {
pageProps: {
encodedSearchResponse: JSON.stringify({
sections: [
{
title: 'Lenses',
sectionType: 6,
results: [
{
resultType: 6,
result: {
"$case": "lens",
lens: {
lensId: "11234567890",
name: "Test Lens 1",
deeplinkUrl: 'https://www.snapchat.com/unlock/?type=SNAPCODE&uuid=example-hash&metadata=01'
}
}
},
{
resultType: 6,
result: {
"$case": "lens",
lens: {
lensId: "21234567890",
name: "Test Lens 2",
deeplinkUrl: 'https://www.snapchat.com/unlock/?type=SNAPCODE&uuid=example-hash&metadata=01'
}
}
}
]
}
]
})
}
}
};
nock('https://www.snapchat.com')
.get('/explore/example-search')
.reply(200, getMockPage(mockJsonData), { 'Content-Type': 'text/html' });
const lenses = await crawler.searchLenses('example-search');
assert(Array.isArray(lenses), 'Result should be an array');
assert.strictEqual(lenses.length, 2, 'Should return 2 lenses');
});
it('should handle a HTTP 400 request gracefully', async () => {
nock('https://example.com')
.get('/should-fail-with-400')
.reply(400, { error: 'Bad Request' });
const result = await crawler.getLensesFromUrl('https://example.com/should-fail-with-400');
assert(result instanceof CrawlerHTTPStatusFailure, 'Result should be an error');
assert.strictEqual(result.code, 400, 'HTTP status code should match');
});
it('should handle a HTTP 404 request gracefully', async () => {
nock('https://example.com')
.get('/should-fail-with-404')
.reply(404, { error: 'Not Found' });
const result = await crawler.getLensesFromUrl('https://example.com/should-fail-with-404');
assert(result instanceof CrawlerNotFoundFailure, 'Result should be an error');
assert.strictEqual(result.code, 404, 'HTTP status code should match');
});
it('should handle a HTTP 500 request gracefully', async () => {
nock('https://example.com')
.get('/should-fail-with-500')
.reply(500, { error: 'Internal Server Error' });
const result = await crawler.getLensesFromUrl('https://example.com/should-fail-with-500');
assert(result instanceof CrawlerHTTPStatusFailure, 'Result should be an error');
assert.strictEqual(result.code, 500, 'HTTP status code should match');
});
});