feedm3/hypem-resolver

View on GitHub
hypem-resolver.js

Summary

Maintainability
B
4 hrs
Test Coverage
/**
 * Copyright 2015 Fabian Dietenberger
 * Available under MIT license
 */

'use strict';

function HypemResolver() {
    var request = require('request'),
        url = require('url'),
        _ = require('lodash'),
        Promise = require('bluebird');

    /** Used for the requests */
    var FIVE_SECONDS_IN_MILLIS = 5000,
        COOKIE = "AUTH=03%3A406b2fe38a1ab80a2953869a475ff110%3A1412624464%3A1469266248%3A01-DE",
        HYPEM_TRACK_URL = "http://hypem.com/track/",
        HYPEM_GO_URL = "http://hypem.com/go/sc/",
        HYPEM_SERVE_URL = "http://hypem.com/serve/source/";

    /**
     * Extract the hypem id from a song's url.
     *
     * @param {string} hypemUrl the url to extract the id
     * @returns {string} the id or "" if no id was found
     */
    function urlToId(hypemUrl) {
        var trimmedUrl = _.trim(hypemUrl);
        if (_.startsWith(trimmedUrl, HYPEM_TRACK_URL)) {
            var parsedUrl = url.parse(hypemUrl);
            var pathname = parsedUrl.pathname; // '/trach/31jfi/...'
            var hypemId = pathname.split("/")[2];
            return hypemId;
        }
        return "";
    }

    /**
     * Get the soundcloud or mp3 url from a song's id.
     *
     * @param hypemId {string} the id of the song
     * @param {Function}[callback] callback function
     * @param {Error} callback.err null if no error occurred
     * @param {string} callback.url the soundcloud or mp3 url
     */
    function getById(hypemId, callback) {
        var options = {
            method: 'HEAD',
            url: HYPEM_GO_URL + hypemId,
            followRedirect: false,
            timeout: FIVE_SECONDS_IN_MILLIS
        };

        request(options, function (error, response) {
            if (error || response.statusCode !== 302) {
                callback(error, null);
                return;
            }
            var songUrl = response.headers.location;
            if (isSoundcloudUrl(songUrl)) {
                var soundcloudUrl = getNormalizedSoundcloudUrl(songUrl);
                callback(null, soundcloudUrl);
            } else {
                requestHypemKey(hypemId, function (error, hypemKey) {
                    if (error) {
                        callback(error, null);
                        return;
                    }
                    requestMp3Url(hypemId, hypemKey, callback);
                });
            }
        });
    }

    return {
        urlToId: urlToId,
        getById: getById,
        getByIdPromise: Promise.promisify(getById)
    };
    /**
     * Get the key for hypem. The key is necessary to request
     * the hypem serve url which gives us the mp3 url. We dont
     * need a key if the song is hosted on soundcloud.
     *
     * @private
     * @param {string} hypemId the id of the song
     * @param {Function}[callback] callback function
     * @param {Error} callback.err null if no error occurred
     * @param {string} callback.url the key to request a hypem song url
     */
    function requestHypemKey(hypemId, callback) {
        var options = {
            method: "GET",
            url: HYPEM_TRACK_URL + hypemId,
            headers: {"Cookie": COOKIE},
            timeout: FIVE_SECONDS_IN_MILLIS
        };

        request(options, function (error, response) {
            if (!error && response.statusCode === 200) {
                var bodyLines = response.body.split('\n');
                _.forIn(bodyLines, function (bodyLine) {
                    if (bodyLine.indexOf('key') !== -1) {
                        // first hit should be the correct one
                        // fix if hypem changes that
                        try {
                            var key = JSON.parse(bodyLine.replace('</script>', '')).tracks[0].key;
                            callback(null, key);
                        } catch (error) {
                            // if an error happen here do nothing and parse
                            // the rest of the document
                        }
                    }
                });
            } else {
                callback(new Error("Nothing found: " + options.url), null);
            }
        });
    }

    /**
     * Get the mp3 url of the song's id with a given key.
     *
     * @private
     * @param {string} hypemId the id of the song
     * @param {string} hypemKey the key to make the request succeed
     * @param {Function}[callback] callback function
     * @param {Error} callback.err null if no error occurred
     * @param {string} callback.url the mp3 url
     */
    function requestMp3Url(hypemId, hypemKey, callback) {
        var options = {
            method: "GET",
            url: HYPEM_SERVE_URL + hypemId + "/" + hypemKey,
            headers: {"Cookie": COOKIE},
            timeout: FIVE_SECONDS_IN_MILLIS
        };

        request(options, function (error, response) {
            if (!error && response.statusCode === 200) {
                try {
                    // the request got a json from hypem
                    // where the link to the mp3 file is saved
                    var jsonBody = JSON.parse(response.body);
                    var mp3Url = jsonBody.url;
                    callback(null, mp3Url);
                } catch (error) {
                    callback(error, null);
                }
            } else {
                callback(new Error("Nothing found: " + options.url), null);
            }
        });
    }

    /**
     * Check if the url is a soundlcoud url. The test is very simple because
     * hypem either returns the complete url or a 'not found' url.
     *
     * @param {string} songUrl the url to check
     * @returns {boolean} true if its a soundcloud url
     */
    function isSoundcloudUrl(songUrl) {
        return songUrl !== "http://soundcloud.com/not/found" &&
            songUrl !== "https://soundcloud.com/not/found";
    }

    /**
     * Get the normalized soundcloud url. This means that every
     * parameter or path which is not necessary gets removed.
     *
     * @private
     * @param {string} soundcloudUrl the url to normalize
     * @returns {string} the normalized soundcloud url
     */
    function getNormalizedSoundcloudUrl(soundcloudUrl) {
        var parsedUrl = url.parse(soundcloudUrl);
        var protocol = parsedUrl.protocol;
        var host = parsedUrl.host;
        var pathname = parsedUrl.pathname;
        var splitHostname = pathname.split('/');
        var normalizedUrl = protocol + "//" + host + "/" + splitHostname[1] + "/" + splitHostname[2];
        return normalizedUrl;
    }
}

module.exports = new HypemResolver();