Comparing version 2.2.0 to 2.2.2
102
lib/index.js
@@ -17,15 +17,20 @@ const fetch = require('node-fetch'); | ||
const headers = { | ||
'Authorization': `Bearer ${this.accessToken}`, | ||
Authorization: `Bearer ${this.accessToken}`, | ||
}; | ||
const { meta, response } = await fetch(url, { headers }) | ||
.then(response => response.json()); | ||
if (meta.status !== 200) { | ||
throw new Error(`${meta.status}: ${meta.message}`); | ||
} | ||
return response; | ||
// Fetch result and parse it as JSON | ||
const body = await fetch(url, { headers }); | ||
const result = await body.json(); | ||
// Handle errors | ||
if (result.error) | ||
throw new Error(`${result.error}: ${result.error_description}`); | ||
if (result.meta.status !== 200) | ||
throw new Error(`${result.meta.status}: ${result.meta.message}`); | ||
return result.response; | ||
} | ||
/* | ||
Search song by ID | ||
Get song by ID | ||
*/ | ||
@@ -36,3 +41,4 @@ | ||
const { song } = await this._request(`songs/${id}?text_format=${textFormat}`); | ||
const path = `songs/${id}?text_format=${textFormat}`; | ||
const { song } = await this._request(path); | ||
@@ -51,5 +57,8 @@ const lyrics = fetchLyrics ? await this._scrapeLyrics(song.url) : null; | ||
const { album } = await this._request(`albums/${id}?text_format=${textFormat}`); | ||
const path = `albums/${id}?text_format=${textFormat}`; | ||
const { album } = await this._request(path); | ||
const tracklist = fetchTracklist ? await this._scrapeTracklist(album.url) : null; | ||
const tracklist = fetchTracklist | ||
? await this._scrapeTracklist(album.url) | ||
: null; | ||
@@ -59,10 +68,23 @@ return Object.assign({ tracklist }, album); | ||
/* Get artist */ | ||
/* Get artist by ID */ | ||
async artist(id, { textFormat = 'dom' } = {}) { | ||
if (!id) throw new Error('No ID was provided to lyricist.artist()'); | ||
const { artist } = await this._request(`artists/${id}?text_format=${textFormat}`); | ||
const path = `artists/${id}?text_format=${textFormat}`; | ||
const { artist } = await this._request(path); | ||
return artist; | ||
} | ||
/* | ||
Get artist by exact name (undocumented, likely to change) | ||
Potentially unreliable, use at own risk! ⚠️ | ||
*/ | ||
async artistByName(name, opts) { | ||
const slug = this._geniusSlug(name); | ||
const id = await this._scrapeArtistPageForArtistID(slug); | ||
return this.artist(id, opts); | ||
} | ||
/* Get artist songs */ | ||
@@ -72,17 +94,21 @@ | ||
if (!id) throw new Error('No ID was provided to lyricist.songsByArtist()'); | ||
const { songs } = await this._request( | ||
`artists/${id}/songs?per_page=${perPage}&page=${page}&sort=${sort}` | ||
); | ||
const path = `artists/${id}/songs?per_page=${perPage}&page=${page}&sort=${sort}`; | ||
const { songs } = await this._request(path); | ||
return songs; | ||
} | ||
/* Search (for songs) */ | ||
async search(query) { | ||
if (!query) throw new Error('No query was provided to lyricist.search()'); | ||
const response = await this._request(`search?q=${query}`); | ||
const path = `search?q=${query}`; | ||
const response = await this._request(path); | ||
return response.hits.map(hit => hit.result); | ||
} | ||
/* | ||
Scrape tracklist | ||
*/ | ||
/* Scrape tracklist */ | ||
@@ -96,3 +122,3 @@ async _scrapeTracklist(url) { | ||
return songs.map(({ song, track_number }) => | ||
Object.assign({ track_number }, song) | ||
Object.assign({ track_number }, song), | ||
); | ||
@@ -107,4 +133,34 @@ } | ||
const $ = cheerio.load(text); | ||
return $('.lyrics').text().trim(); | ||
return $('.lyrics') | ||
.text() | ||
.trim(); | ||
} | ||
} | ||
/* Get slug from name/title */ | ||
_geniusSlug(string) { | ||
// Probably not 100% accurate yet | ||
// Currently only used by undocumented artistByName function | ||
const slug = string | ||
.trim() | ||
.replace(/\s+/g, '-') | ||
.replace("'", '') | ||
.replace(/[^a-zA-Z0-9]/g, '-') | ||
.toLowerCase(); | ||
// Uppercase first letter | ||
return slug.charAt(0).toUpperCase() + slug.slice(1); | ||
} | ||
/* Scrape artist page to retrieve artist ID */ | ||
async _scrapeArtistPageForArtistID(slug) { | ||
const url = `https://genius.com/artists/${slug}`; | ||
const html = await fetch(url).then(res => res.text()); | ||
const $ = cheerio.load(html); | ||
const id = $('meta[name="newrelic-resource-path"]') | ||
.attr('content') | ||
.split('/') | ||
.pop(); | ||
return id; | ||
} | ||
}; |
@@ -13,4 +13,15 @@ const Lyricist = require('./'); | ||
test('invalid request should return 403 status and throw error', async () => { | ||
await expect(lyricist._request('abc')).toThrow(); | ||
test('invalid request should return 403 status and throw error', () => | ||
expect(lyricist._request('abc')).rejects.toEqual( | ||
new Error('403: Action forbidden for current scope'), | ||
)); | ||
test('request with invalid auth key should throw a helpful error', async done => { | ||
const lyricist = new Lyricist("I'm an invalid auth key"); | ||
try { | ||
await lyricist._request('songs/1'); | ||
} catch (e) { | ||
expect(e.message).toMatch('invalid_token'); | ||
done(); | ||
} | ||
}); | ||
@@ -25,3 +36,5 @@ | ||
test('scrape an album tracklist and return an array of songs', async () => { | ||
const result = await lyricist._scrapeTracklist('http://genius.com/albums/Son-lux/Lanterns'); | ||
const result = await lyricist._scrapeTracklist( | ||
'http://genius.com/albums/Son-lux/Lanterns', | ||
); | ||
expect(result).toBeInstanceOf(Array); | ||
@@ -34,3 +47,5 @@ expect(result.length > 0).toBeTruthy(); | ||
test('scrape song lyrics and return text', async () => { | ||
const result = await lyricist._scrapeLyrics('https://genius.com/Son-lux-alternate-world-lyrics'); | ||
const result = await lyricist._scrapeLyrics( | ||
'https://genius.com/Son-lux-alternate-world-lyrics', | ||
); | ||
expect(result).toMatch(/Alternate world/); | ||
@@ -40,4 +55,25 @@ }); | ||
describe('_geniusSlug', () => { | ||
test('converts a name/title to the slug used by genius.com', async () => { | ||
const namesAndExpectedSlugs = { | ||
'Kanye West': 'Kanye-west', | ||
'A$AP Rocky': 'A-ap-rocky', | ||
'Alt J': 'Alt-j', | ||
}; | ||
const names = Object.keys(namesAndExpectedSlugs); | ||
const expectedSlugs = names.map(name => namesAndExpectedSlugs[name]); | ||
const slugs = names.map(lyricist._geniusSlug); | ||
expect(slugs).toEqual(expectedSlugs); | ||
}); | ||
}); | ||
describe('_scrapeArtistPageForArtistID', () => { | ||
test('scrape an artist page and return their genius ID', async () => { | ||
const result = await lyricist._scrapeArtistPageForArtistID('Son-lux'); | ||
expect(result).toEqual('94423'); | ||
}); | ||
}); | ||
describe('song()', () => { | ||
test('throw an error if no ID is given', async (done) => { | ||
test('throw an error if no ID is given', async done => { | ||
try { | ||
@@ -63,3 +99,3 @@ await lyricist.song(); | ||
describe('album()', () => { | ||
test('throw an error if no ID is given', async (done) => { | ||
test('throw an error if no ID is given', async done => { | ||
try { | ||
@@ -86,3 +122,3 @@ await lyricist.album(); | ||
describe('artist()', () => { | ||
test('throw an error if no ID is given', async (done) => { | ||
test('throw an error if no ID is given', async done => { | ||
try { | ||
@@ -100,10 +136,19 @@ await lyricist.artist(); | ||
}); | ||
}) | ||
}); | ||
describe('artistByName()', () => { | ||
test('load artist by name', async () => { | ||
const result = await lyricist.artistByName('Son Lux'); | ||
expect(result.api_path).toEqual('/artists/94423'); | ||
}); | ||
}); | ||
describe('songsByArtist()', () => { | ||
test('throw an error if no ID is given', async (done) => { | ||
test('throw an error if no ID is given', async done => { | ||
try { | ||
await lyricist.songsByArtist(); | ||
} catch (e) { | ||
expect(e.message).toEqual('No ID was provided to lyricist.songsByArtist()'); | ||
expect(e.message).toEqual( | ||
'No ID was provided to lyricist.songsByArtist()', | ||
); | ||
done(); | ||
@@ -113,3 +158,3 @@ } | ||
test('load artist songs', async() => { | ||
test('load artist songs', async () => { | ||
const result = await lyricist.songsByArtist(2); | ||
@@ -119,3 +164,3 @@ expect(result[0]).toHaveProperty('title'); | ||
test('perPage option', async() => { | ||
test('perPage option', async () => { | ||
const perPage = 10; | ||
@@ -126,10 +171,12 @@ const result = await lyricist.songsByArtist(2, { perPage }); | ||
test('page option', async() => { | ||
test('page option', async () => { | ||
const page = 2; | ||
const result = await lyricist.songsByArtist(2, { page }); | ||
expect(result.find(t => t.title === 'Addicted to the Game').id).toEqual(3129); | ||
expect(result.find(t => t.title === 'Addicted to the Game').id).toEqual( | ||
3129, | ||
); | ||
}); | ||
test('sort option', async() => { | ||
const sort = 'popularity' | ||
test('sort option', async () => { | ||
const sort = 'popularity'; | ||
const result = await lyricist.songsByArtist(2); | ||
@@ -144,3 +191,3 @@ const resultWithSort = await lyricist.songsByArtist(2, { sort }); | ||
describe('search()', () => { | ||
test('throw an error if no search term is given', async (done) => { | ||
test('throw an error if no search term is given', async done => { | ||
try { | ||
@@ -155,7 +202,8 @@ await lyricist.search(); | ||
test('return search results', async () => { | ||
const result = await lyricist.search('spirit of my silence I can hear you'); | ||
const result = await lyricist.search( | ||
'spirit of my silence I can hear you', | ||
); | ||
expect(result[0].primary_artist.name).toEqual('Sufjan Stevens'); | ||
}); | ||
}); | ||
}); |
@@ -24,17 +24,14 @@ 'use strict'; | ||
const headers = { | ||
'Authorization': `Bearer ${_this.accessToken}` | ||
Authorization: `Bearer ${_this.accessToken}` | ||
}; | ||
var _ref = yield fetch(url, { headers }).then(function (response) { | ||
return response.json(); | ||
}); | ||
// Fetch result and parse it as JSON | ||
const body = yield fetch(url, { headers }); | ||
const result = yield body.json(); | ||
const meta = _ref.meta, | ||
response = _ref.response; | ||
// Handle errors | ||
if (result.error) throw new Error(`${result.error}: ${result.error_description}`); | ||
if (result.meta.status !== 200) throw new Error(`${result.meta.status}: ${result.meta.message}`); | ||
if (meta.status !== 200) { | ||
throw new Error(`${meta.status}: ${meta.message}`); | ||
} | ||
return response; | ||
return result.response; | ||
})(); | ||
@@ -44,3 +41,3 @@ } | ||
/* | ||
Search song by ID | ||
Get song by ID | ||
*/ | ||
@@ -54,7 +51,9 @@ | ||
var _ref2 = yield _this2._request(`songs/${id}?text_format=${textFormat}`); | ||
const path = `songs/${id}?text_format=${textFormat}`; | ||
const song = _ref2.song; | ||
var _ref = yield _this2._request(path); | ||
const song = _ref.song; | ||
const lyrics = fetchLyrics ? yield _this2._scrapeLyrics(song.url) : null; | ||
@@ -76,7 +75,9 @@ | ||
var _ref3 = yield _this3._request(`albums/${id}?text_format=${textFormat}`); | ||
const path = `albums/${id}?text_format=${textFormat}`; | ||
const album = _ref3.album; | ||
var _ref2 = yield _this3._request(path); | ||
const album = _ref2.album; | ||
const tracklist = fetchTracklist ? yield _this3._scrapeTracklist(album.url) : null; | ||
@@ -88,3 +89,3 @@ | ||
/* Get artist */ | ||
/* Get artist by ID */ | ||
@@ -97,6 +98,8 @@ artist(id, { textFormat = 'dom' } = {}) { | ||
var _ref4 = yield _this4._request(`artists/${id}?text_format=${textFormat}`); | ||
const path = `artists/${id}?text_format=${textFormat}`; | ||
const artist = _ref4.artist; | ||
var _ref3 = yield _this4._request(path); | ||
const artist = _ref3.artist; | ||
return artist; | ||
@@ -106,6 +109,21 @@ })(); | ||
/* | ||
Get artist by exact name (undocumented, likely to change) | ||
Potentially unreliable, use at own risk! ⚠️ | ||
*/ | ||
artistByName(name, opts) { | ||
var _this5 = this; | ||
return _asyncToGenerator(function* () { | ||
const slug = _this5._geniusSlug(name); | ||
const id = yield _this5._scrapeArtistPageForArtistID(slug); | ||
return _this5.artist(id, opts); | ||
})(); | ||
} | ||
/* Get artist songs */ | ||
songsByArtist(id, { page = 1, perPage = 20, sort = 'title' } = {}) { | ||
var _this5 = this; | ||
var _this6 = this; | ||
@@ -115,6 +133,9 @@ return _asyncToGenerator(function* () { | ||
var _ref5 = yield _this5._request(`artists/${id}/songs?per_page=${perPage}&page=${page}&sort=${sort}`); | ||
const path = `artists/${id}/songs?per_page=${perPage}&page=${page}&sort=${sort}`; | ||
const songs = _ref5.songs; | ||
var _ref4 = yield _this6._request(path); | ||
const songs = _ref4.songs; | ||
return songs; | ||
@@ -124,8 +145,13 @@ })(); | ||
/* Search (for songs) */ | ||
search(query) { | ||
var _this6 = this; | ||
var _this7 = this; | ||
return _asyncToGenerator(function* () { | ||
if (!query) throw new Error('No query was provided to lyricist.search()'); | ||
const response = yield _this6._request(`search?q=${query}`); | ||
const path = `search?q=${query}`; | ||
const response = yield _this7._request(path); | ||
return response.hits.map(function (hit) { | ||
@@ -137,5 +163,3 @@ return hit.result; | ||
/* | ||
Scrape tracklist | ||
*/ | ||
/* Scrape tracklist */ | ||
@@ -167,2 +191,26 @@ _scrapeTracklist(url) { | ||
} | ||
/* Get slug from name/title */ | ||
_geniusSlug(string) { | ||
// Probably not 100% accurate yet | ||
// Currently only used by undocumented artistByName function | ||
const slug = string.trim().replace(/\s+/g, '-').replace("'", '').replace(/[^a-zA-Z0-9]/g, '-').toLowerCase(); | ||
// Uppercase first letter | ||
return slug.charAt(0).toUpperCase() + slug.slice(1); | ||
} | ||
/* Scrape artist page to retrieve artist ID */ | ||
_scrapeArtistPageForArtistID(slug) { | ||
return _asyncToGenerator(function* () { | ||
const url = `https://genius.com/artists/${slug}`; | ||
const html = yield fetch(url).then(function (res) { | ||
return res.text(); | ||
}); | ||
const $ = cheerio.load(html); | ||
const id = $('meta[name="newrelic-resource-path"]').attr('content').split('/').pop(); | ||
return id; | ||
})(); | ||
} | ||
}; |
{ | ||
"name": "lyricist", | ||
"version": "2.2.0", | ||
"version": "2.2.2", | ||
"description": "Fetches song lyrics using the Genius.com API and website.", | ||
@@ -14,10 +14,9 @@ "main": "lib/index.js", | ||
"test:coverage": "jest --coverage", | ||
"build": "rimraf node6 && babel lib --out-dir node6 --ignore *.test.js" | ||
"build": "rimraf node6 && babel lib --out-dir node6 --ignore *.test.js", | ||
"precommit": "lint-staged" | ||
}, | ||
"keywords": [ | ||
"lyric", | ||
"lyrics", | ||
"scrape", | ||
"genius" | ||
], | ||
"lint-staged": { | ||
"lib/*.js": ["prettier --write", "git add"] | ||
}, | ||
"keywords": ["lyric", "lyrics", "scrape", "genius"], | ||
"author": "scf4 (https://github.com/scf4)", | ||
@@ -31,3 +30,2 @@ "license": "MIT", | ||
"cheerio": "^0.22.0", | ||
"lyricist": "^2.0.0", | ||
"node-fetch": "^1.6.3" | ||
@@ -37,9 +35,12 @@ }, | ||
"babel-cli": "^6.24.1", | ||
"babel-jest": "^21.0.2", | ||
"babel-polyfill": "^6.23.0", | ||
"babel-preset-env": "^1.5.2", | ||
"babel-jest": "^19.0.0", | ||
"babel-polyfill": "^6.23.0", | ||
"dotenv": "^4.0.0", | ||
"jest": "^19.0.2", | ||
"husky": "^0.14.3", | ||
"jest": "^21.1.0", | ||
"lint-staged": "^4.1.3", | ||
"prettier": "^1.6.1", | ||
"rimraf": "^2.6.1" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2
441
22167
10
9
8
- Removedlyricist@^2.0.0