├── .gitignore ├── index.js ├── vercel.json ├── src ├── utils │ └── response.js ├── middlewares.js ├── app.js └── controllers │ └── indexController.js ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | base.js 3 | .env -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const app = require('./src/app'); 2 | 3 | const port = process.env.PORT || 5000; 4 | app.listen(port, () => { 5 | console.log(`Listening on port:${port}`); 6 | }); 7 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version":2, 3 | "builds":[ 4 | { 5 | "src":"./index.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes":[ 10 | { 11 | "src":"/(.*)", 12 | "dest":"/" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /src/utils/response.js: -------------------------------------------------------------------------------- 1 | const json = (res, data) => { 2 | res.json({ 3 | status: true, 4 | data, 5 | }); 6 | }; 7 | 8 | const errorJson = (res, error, status = 500) => { 9 | res.status(status).json({ 10 | status: false, 11 | error: `Something went wrong: ${error}`, 12 | }); 13 | }; 14 | 15 | module.exports = { 16 | json, 17 | errorJson 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Song Lyrics API 2 | 3 | ## 1. Hot Song 4 | ``` 5 | [ENDPOINT] /hot 6 | ``` 7 | ``` 8 | [GET] https://api-song-lyrics.herokuapp.com/hot 9 | ``` 10 | 11 | ## 2. Lyrics 12 | ``` 13 | [ENDPOINT] /lyrics/:id 14 | ``` 15 | ``` 16 | [GET] https://api-song-lyrics.herokuapp.com/lyrics/-c-chrisye-kala+cinta+menggoda_20227653 17 | ``` 18 | 19 | ## 3. Search Song 20 | ``` 21 | [ENDPOINT] /search?q={query} 22 | ``` 23 | ``` 24 | [GET] https://api-song-lyrics.herokuapp.com/search?q=Kala cinta menggoda 25 | ``` 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "song-lyrics-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon src/index", 8 | "start": "node src/index" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cheerio": "^1.0.0-rc.5", 15 | "cors": "^2.8.5", 16 | "dotenv": "^8.2.0", 17 | "express": "^4.17.1", 18 | "helmet": "^4.3.1", 19 | "morgan": "^1.10.0", 20 | "nodemon": "^2.0.7", 21 | "request": "^2.88.2", 22 | "request-promise": "^4.2.6" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/middlewares.js: -------------------------------------------------------------------------------- 1 | function notFound(req, res, next) { 2 | res.status(404); 3 | const error = new Error(`🔍 - Not Found - ${req.originalUrl}`); 4 | next(error); 5 | } 6 | 7 | /* eslint-disable no-unused-vars */ 8 | function errorHandler(err, req, res, next) { 9 | /* eslint-enable no-unused-vars */ 10 | const statusCode = res.statusCode !== 200 ? res.statusCode : 500; 11 | res.status(statusCode); 12 | res.json({ 13 | message: err.message, 14 | stack: process.env.NODE_ENV === "production" ? "🥞" : err.stack, 15 | }); 16 | } 17 | 18 | module.exports = { 19 | notFound, 20 | errorHandler, 21 | }; 22 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const morgan = require("morgan"); 3 | const helmet = require("helmet"); 4 | const cors = require("cors"); 5 | const indexController = require('./controllers/indexController'); 6 | 7 | require("dotenv").config(); 8 | 9 | const middlewares = require("./middlewares"); 10 | 11 | const app = express(); 12 | 13 | app.use(morgan("dev")); 14 | app.use(helmet()); 15 | app.use(cors()); 16 | app.use(express.json()); 17 | 18 | app.get("/", indexController.index); 19 | app.get("/hot", indexController.hotLyrics); 20 | app.get("/lyrics/:id?", indexController.detailLyrics); 21 | app.get("/search", indexController.searchLyrics); 22 | app.get("/test", indexController.test); 23 | 24 | app.use(middlewares.notFound); 25 | app.use(middlewares.errorHandler); 26 | 27 | module.exports = app; 28 | -------------------------------------------------------------------------------- /src/controllers/indexController.js: -------------------------------------------------------------------------------- 1 | const request = require("request-promise"); 2 | const cheerio = require("cheerio"); 3 | const { json, errorJson } = require('../utils/response'); 4 | 5 | exports.index = (req, res) => { 6 | const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl; 7 | 8 | return json(res, { 9 | maintainer: 'Azhari Muhammad M ', 10 | source: 'https://github.com/azharimm/song-lyrics-api', 11 | hot_lyrics: { 12 | endpoint: '/hot', 13 | example: fullUrl+'hot' 14 | }, 15 | detail_lyrics: { 16 | endpoint: '/lyrics/:id', 17 | example: fullUrl+'lyrics/-c-chrisye-kala+cinta+menggoda_20227653' 18 | }, 19 | search_lyrics: { 20 | endpoint: '/lyrics/search', 21 | example: fullUrl+'search?q=Kala cinta menggoda' 22 | } 23 | }); 24 | } 25 | 26 | exports.hotLyrics = async (req, res) => { 27 | const baseUrl = req.protocol + '://' + req.get('host'); 28 | const htmlResult = await request.get({ 29 | uri: `${process.env.BASE_URL}/top`, 30 | headers: { 31 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36' 32 | }, 33 | }); 34 | const $ = await cheerio.load(htmlResult); 35 | const hotsongLists = []; 36 | $(".lf-list__row").each((index, el) => { 37 | let songId = $(el) 38 | .children(".lf-list__subtitle") 39 | .children("a") 40 | .attr("href") 41 | 42 | let artist = $(el) 43 | .children(".lf-list__title") 44 | .children("span") 45 | .text() 46 | .replace(/\s+/g, "") 47 | .replace(/([A-Z])/g, " $1") 48 | .trim(); 49 | let songTitle = $(el) 50 | .children(".lf-list__subtitle") 51 | .children("a") 52 | .text() 53 | .replace(/\s+/g, "") 54 | .replace(/([A-Z])/g, " $1") 55 | .trim() 56 | .replace("Lyrics", ""); 57 | if(index > 0) { 58 | let id = songId.split(".html")[0] 59 | .replace(/\//g, "-") 60 | hotsongLists.push({ 61 | songId: id, 62 | artist, 63 | songTitle, 64 | songLyrics: `${baseUrl}/lyrics/${id}` 65 | }); 66 | } 67 | }); 68 | return json(res, hotsongLists); 69 | } 70 | 71 | exports.detailLyrics = async (req, res) => { 72 | let id = req.params.id; 73 | if(!id) { 74 | return errorJson(res, "Mohon isi song id"); 75 | } 76 | try { 77 | songId = id.replace(/-/g, "/")+'.html'; 78 | const htmlResult = await request.get( 79 | `${process.env.BASE_URL}${songId}` 80 | ); 81 | const $ = await cheerio.load(htmlResult); 82 | const artist = $(".lyric-song-head").children("a").text(); 83 | const songTitle = $(".lyric-song-head").text().split("–")[1].replace("Lyrics", "").trim(); 84 | const songLyrics = $("#content").text().trim(); 85 | const songLyricsArr = songLyrics.split("\n"); 86 | return json(res, { 87 | artist, 88 | songTitle, 89 | songLyrics, 90 | songLyricsArr, 91 | }); 92 | } catch (error) { 93 | return errorJson(res, "Mohon isi id dengan valid id"); 94 | } 95 | } 96 | 97 | exports.searchLyrics = async (req, res) => { 98 | const baseUrl = req.protocol + '://' + req.get('host'); 99 | const { q } = req.query; 100 | if(!q) { 101 | return errorJson(res, "Mohon isi query pencarian!"); 102 | } 103 | const htmlResult = await request.get( 104 | `${process.env.BASE_URL}/search.php?q=${q}` 105 | ); 106 | const $ = await cheerio.load(htmlResult); 107 | const resultLists = []; 108 | $(".lf-list__row").each((index, el) => { 109 | let songId = $(el) 110 | .children(".lf-list__meta") 111 | .children("a") 112 | .attr("href") 113 | 114 | let artist = $(el) 115 | .children(".lf-list__title--secondary") 116 | .children("a") 117 | .text() 118 | .replace(/\s+/g, "") 119 | .replace(/([A-Z])/g, " $1") 120 | .trim(); 121 | let songTitle = $(el) 122 | .children(".lf-list__meta") 123 | .children("a") 124 | .text() 125 | .replace(/\s+/g, "") 126 | .replace(/([A-Z])/g, " $1") 127 | .trim() 128 | .replace("Lyrics", ""); 129 | if(index > 0) { 130 | let id = songId.split(".html")[0] 131 | .replace(/\//g, "-") 132 | resultLists.push({ 133 | songId: id, 134 | artist, 135 | songTitle, 136 | songLyrics: `${baseUrl}/lyrics/${id}` 137 | }); 138 | } 139 | }); 140 | 141 | return json(res, resultLists); 142 | } 143 | 144 | exports.test = async (req, res) => { 145 | const htmlResult = await request.get({ 146 | uri: `${process.env.BASE_URL_V2}`, 147 | headers: { 148 | 'User-Agent':"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30" 149 | }, 150 | }); 151 | const $ = await cheerio.load(htmlResult); 152 | const resultLists = []; 153 | $(".list-group-item").each((index, el) => { 154 | const text = $(el).text(); 155 | resultLists.push(text); 156 | }); 157 | 158 | return json(res, resultLists); 159 | } --------------------------------------------------------------------------------