Comparing version 2.0.0 to 2.1.0
@@ -1,8 +0,8 @@ | ||
import fetch from 'node-fetch'; | ||
import cheerio from 'cheerio'; | ||
const fetch = require('node-fetch'); | ||
const cheerio = require('cheerio'); | ||
export default class Lyricist { | ||
module.exports = class Lyricist { | ||
constructor(accessToken) { | ||
if (!accessToken) throw new Error('No access token provided!'); | ||
this.accessToken = accessToken; | ||
if (!accessToken) throw new Error('No access token provided to lyricist!'); | ||
this.accessToken = accessToken; | ||
} | ||
@@ -16,11 +16,12 @@ | ||
const url = `https://api.genius.com/${path}`; | ||
const response = await fetch(url, { | ||
headers: { 'Authorization': `Bearer ${this.accessToken}` } | ||
}); | ||
const json = await response.text(); | ||
const parsed = JSON.parse(json); | ||
if (parsed.meta.status !== 200) { | ||
// throw new Error(parsed.meta.status); | ||
const headers = { | ||
'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 parsed.response; | ||
return response; | ||
} | ||
@@ -32,12 +33,8 @@ | ||
async song(id, opts) { | ||
async song(id, { fetchLyrics = 123 } = {}) { | ||
if (!id) throw new Error('No ID was provided to lyricist.song()'); | ||
const path = `songs/${id}`; | ||
const response = await this._request(path); | ||
const song = response.song; | ||
const { song } = await this._request(`songs/${id}`); | ||
let lyrics = null; | ||
if (opts && opts.fetchLyrics) | ||
lyrics = await this._scrapeLyrics(song.url); | ||
const lyrics = fetchLyrics ? await this._scrapeLyrics(song.url) : null; | ||
@@ -51,12 +48,8 @@ return Object.assign({ lyrics }, song); | ||
async album(id, opts) { | ||
async album(id, { fetchTracklist = false } = {}) { | ||
if (!id) throw new Error('No ID was provided to lyricist.album()'); | ||
const path = `albums/${id}`; | ||
const response = await this._request(path); | ||
const album = response.album; | ||
const { album } = await this._request(`albums/${id}`); | ||
let tracklist = null; | ||
if (opts && opts.fetchTracklist) | ||
tracklist = await this._scrapeTracklist(album.url); | ||
const tracklist = fetchTracklist ? await this._scrapeTracklist(album.url) : null; | ||
@@ -70,6 +63,4 @@ return Object.assign({ tracklist }, album); | ||
if (!id) throw new Error('No ID was provided to lyricist.artist()'); | ||
const path = `artists/${id}`; | ||
const response = await this._request(path); | ||
return response.artist; | ||
const { artist } = await this._request(`artists/${id}`); | ||
return artist; | ||
} | ||
@@ -79,20 +70,13 @@ | ||
async songsByArtist(id, opts) { | ||
async songsByArtist(id, { page = 1, perPage = 20, sort = 'title' } = {}) { | ||
if (!id) throw new Error('No ID was provided to lyricist.songsByArtist()'); | ||
const page = opts && opts.page || 1; | ||
const perPage = opts && opts.perPage || 20; | ||
const sort = opts && opts.sort || 'title'; | ||
const path = `artists/${id}/songs?per_page=${perPage}&page=${page}&sort=${sort}`; | ||
const response = await this._request(path); | ||
const songs = response.songs; | ||
const { songs } = await this._request( | ||
`artists/${id}/songs?per_page=${perPage}&page=${page}&sort=${sort}` | ||
); | ||
return songs; | ||
} | ||
async search(query, opts) { | ||
if (!query) throw new Error('No search query was provided to lyricist.search()'); | ||
const path = `search?q=${query}`; | ||
const response = await this._request(path); | ||
async search(query) { | ||
if (!query) throw new Error('No query was provided to lyricist.search()'); | ||
const response = await this._request(`search?q=${query}`); | ||
return response.hits.map(hit => hit.result); | ||
@@ -106,10 +90,10 @@ } | ||
async _scrapeTracklist(url) { | ||
const response = await fetch(url); | ||
const text = await response.text(); | ||
const $ = cheerio.load(text); | ||
const songs = $('.primary_list li').map((i, el) => ({ | ||
id: $(el).data('id'), | ||
title: $(el).find('.song_title').text(), | ||
})); | ||
return songs.toArray(); | ||
const html = await fetch(url).then(res => res.text()); | ||
const $ = cheerio.load(html); | ||
const json = $('meta[itemprop="page_data"]').attr('content'); | ||
const parsed = JSON.parse(json); | ||
const songs = parsed.album_appearances; | ||
return songs.map(({ song, track_number }) => | ||
Object.assign({ track_number }, song) | ||
); | ||
} | ||
@@ -116,0 +100,0 @@ |
@@ -1,6 +0,6 @@ | ||
import Lyricist from '../'; | ||
import authKey from './test_key'; | ||
const Lyricist = require('./'); | ||
require('dotenv').config(); | ||
describe('Lyricist', () => { | ||
const lyricist = new Lyricist(authKey); | ||
const lyricist = new Lyricist(process.env.TEST_KEY); | ||
@@ -14,7 +14,7 @@ test('initiating without an authKey should throw an error', () => { | ||
test('invalid request should return 403 status and throw error', async () => { | ||
await expect(lyricist._request({ path: '/abc' })).toThrow(); | ||
await expect(lyricist._request('abc')).toThrow(); | ||
}); | ||
test('successful requests should return object', async () => { | ||
const result = await lyricist.song(714198); | ||
const result = await lyricist._request('songs/1'); | ||
expect(result).toBeInstanceOf(Object); | ||
@@ -27,2 +27,3 @@ }); | ||
expect(result).toBeInstanceOf(Array); | ||
expect(result.length > 0).toBeTruthy(); | ||
}); | ||
@@ -49,3 +50,3 @@ }); | ||
test('load a song', async () => { | ||
const result = await lyricist.song(714198); | ||
const result = await lyricist.song(714198, {}); | ||
expect(result).toHaveProperty('title'); | ||
@@ -140,3 +141,3 @@ }); | ||
} catch (e) { | ||
expect(e.message).toEqual('No search query was provided to lyricist.search()'); | ||
expect(e.message).toEqual('No query was provided to lyricist.search()'); | ||
done(); | ||
@@ -143,0 +144,0 @@ } |
{ | ||
"name": "lyricist", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "Fetches song lyrics using the Genius.com API and website.", | ||
@@ -14,3 +14,3 @@ "main": "dist/index.js", | ||
"test:coverage": "jest --coverage", | ||
"build": "rimraf dist && babel lib --out-dir dist --ignore *.test.js" | ||
"build": "rimraf node6 && babel lib --out-dir node6 --ignore *.test.js" | ||
}, | ||
@@ -31,5 +31,8 @@ "keywords": [ | ||
"cheerio": "^0.22.0", | ||
"lyricist": "^2.0.0", | ||
"node-fetch": "^1.6.3" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.24.1", | ||
"babel-preset-env": "^1.5.2", | ||
"babel-jest": "^19.0.0", | ||
@@ -41,2 +44,3 @@ "babel-plugin-transform-object-rest-spread": "^6.23.0", | ||
"babel-preset-es2017": "^6.24.1", | ||
"dotenv": "^4.0.0", | ||
"jest": "^19.0.2", | ||
@@ -43,0 +47,0 @@ "rimraf": "^2.6.1" |
# Lyricist 🎤 | ||
⭐️ Genius.com API client (with lyrics scraper) | ||
⭐️ Genius.com API client with lyrics scraper | ||
## v2.0 | ||
Version 2.0 is a complete rewrite with promises and async/await. | ||
Version 2.0 is a complete rewrite with async/await. | ||
@@ -19,2 +19,8 @@ ## Installation | ||
## Node <= 6 | ||
Older versions of node don't support async/await and will need to use the transpiled version (lyricist/node6), along with promises: | ||
```js | ||
const Lyricist = require('lyricist/node6'); | ||
``` | ||
## API Key | ||
@@ -24,3 +30,2 @@ Get an access token at https://genius.com/api-clients. | ||
```js | ||
import Lyricist from 'lyricist'; | ||
const lyricist = new Lyricist(accessToken); | ||
@@ -34,10 +39,10 @@ ``` | ||
console.log(song.title); | ||
// output: Death with Dignity | ||
``` | ||
#### or with promises: | ||
#### or with promises for node <= 6: | ||
```js | ||
lyricist.song(714198).then(song => console.log(song.title)); | ||
``` | ||
> Note: The rest of the examples use async/await for readability. | ||
@@ -47,3 +52,4 @@ ## Get song lyrics | ||
```js | ||
const lyrics = await lyricist.song(714198, { fetchLyrics: true }); | ||
const song = await lyricist.song(714198, { fetchLyrics: true }); | ||
console.log(song.lyrics); | ||
@@ -58,3 +64,3 @@ // output: Spirit of my silence I can hear you... | ||
const album = await lyricist.album(56682); | ||
console.log('%s by %s, album.name, album.artist.name); | ||
console.log('%s by %s', album.name, album.artist.name); | ||
@@ -71,3 +77,3 @@ // output: Lanterns by Son Lux | ||
// output: [{ id: 502102, title: 'Alternate World' }, { id: 267773, title: 'Lost It To Trying' }, ...] | ||
// output: [{ id: 502102, title: 'Alternate World', ... }, { id: 267773, title: 'Lost It To Trying', ... }, ...] | ||
@@ -78,3 +84,3 @@ ``` | ||
```js | ||
const artist = lyricist.artist(2); | ||
const artist = await lyricist.artist(2); | ||
console.log(artist.name); | ||
@@ -87,3 +93,3 @@ // output: Jay Z | ||
```js | ||
const songs = lyricist.songsByArtist(2); | ||
const songs = await lyricist.songsByArtist(2); | ||
``` | ||
@@ -90,0 +96,0 @@ `songsByArtist()` will show **20 results per page** by default, and can be as high as 50. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
122092
11
107
3
11
322
1
7
+ Addedlyricist@^2.0.0