Jump to content

Extracting media URL from performance link

Recommended Posts

Hauritzius

Hi all, and thanks for a great site!

I was kinda wondering if any of the good folks (like @OpenType) would be willing to share some secrets on how the media URL is found. I'm extracting a URL from the meta tag that points to twitter-stream, but even though it looks very similar at first glance it is not the same URL as sing.salon generates. 

Basically what I'm doing is making a Telegram bot that will download and send the media to whoever asks for it. In addition to downloading, I also want to extract the information about the performance and write it to the downloaded files' metadata before sending it to the client. Of course, I'm not hosting that bot for anyone as it would incur a lot of hosting costs, but I'd upload the source to GitHub of course. 

Is the URL in the content attribute encrypted or encoded in some way? 

Hope it is OK to ask this. Thanks! 

  • like 1
Link to comment
Share on other sites

Hauritzius

Nvm - I figured it out on my own. Then I wrote a console app for Windows that takes an URL, downloads the file, extracts the metadata from the website and uses it to tag and rename the file into something a lot more useful than a random string of numbers. Still needs some work, but it certainly works. W00t!

Link to comment
Share on other sites

  • 9 months later...
Hauritzius
8 hours ago, _Slynn_ said:

Any progress on this? I was trying to build a bot from an old github, but it had an old way to extract the url, and smule has changed it since then.

Still working on it, and I'll release it as an Android app in Play Store soon-ish

Link to comment
Share on other sites

Gwapo_Erik
//Maybe usefull for someone, but here you go a perfect way to use Smule's own code to decode the media url and download a performance.

//tests
//video
//await downloadPerformance('https://www.smule.com/recording/ava-max-sweet-but-psycho/1998769355_3504039030');
//await downloadPerformance('1998769355_3504039030');

//audio
//await downloadPerformance('https://www.smule.com/recording/paper-hearts-guitar-acoustic/823670826_3905415470');
//await downloadPerformance('823670826_3905415470');


//go to smule, copy and paste in console
async function downloadPerformance(urlOrKey) {
    await init();

    var pKey = urlOrKey.split('/').pop();
    var r = await (await fetch(`/p/${pKey}/json`)).json();
    var mediaUrl = r.type == 'video' ? r.video_media_url : r.media_url;
    var decodedMediaUrl = decodeMediaUrl(mediaUrl);

    window.open(decodedMediaUrl, '_blank');
}

async function init() {
    var resolveDecodeMediaUrlFn = async () => {
        return new Promise((resolve, reject) => {
            var resolveDecode = (version, modules) => {
                var decodeMediaUrl;
                for (let id in modules) {
                    let module = modules[id];
                    let exports = module.exports;
                    if (!exports || typeof exports != 'object' || exports == window) continue;

                    if (version == 'v4') {
                        for (let p in exports) {
                            let pv = exports[p];
                            if (pv && typeof pv == 'string' && pv == 'X-CSRF-Token') {
                                if (typeof decodeMediaUrl != 'undefined') continue;

                                for (var p2 in exports) {
                                    if (typeof exports[p2] != 'function') continue;
                                    var fn = exports[p2];
                                    if (fn.toString().indexOf('Failed to decode URL') == -1) continue;
                                    resolve((url) => { return fn(url); });
                                    return;
                                }
                            }
                        }
                    } else if (version == 'v3') {
                        if (!exports.hasOwnProperty('__esModule')) continue;

                        if (exports.hasOwnProperty('CSRF_TOKEN_HEADER')) {
                            resolve((url) => { return exports['processRecording'](url); });
                            return;
                        }
                    }
                }

                throw new Error('decode not found');
            }

            var wp = window.webpackJsonp;
            var mId = 'dummy-' + new Date().getTime();
            if (typeof wp === 'function') {
                wp([], { [mId]: function () { var require = arguments[2]; resolveDecode("v3", require.c); } }, [mId]);
            } else {
                wp.push([[mId], { [mId]: function () { var require = arguments[2]; resolveDecode("v4", require.c); } }, [[mId]]]);
            }
        });
    };

    if (!window.decodeMediaUrl) {
        var r = await resolveDecodeMediaUrlFn();
        window.decodeMediaUrl = r;
    }

    return window.decodeMediaUrl;
}

 

Link to comment
Share on other sites

_Slynn_

I'm trying to build a telegram bot. and the only struggle I am having is extracting that hidden download link so I can send it back in a message.

I'm using Python

Edited by _Slynn_
Link to comment
Share on other sites

Gwapo_Erik

It is possible to reverse engineer the decoding process. But it's not easy work. And the code for decoding the URL's get updated from time to time. So you have to fix your app every time Smule pushes a new update. I think you could take a view different routes. 

1) The easiest one is to contact Smule and pay for API access. That is if they allow something like that.

2) You could try to extract the decoding logic from the core Smule javascript app, and run that in a JavaScript interpreter in side you Python application. You could even automate that process inside of your bot.

3) Use a version of Chromium that you can run in memory, open the Smule website in the background. And run the code i shared earlier. Extract the result. That will eat lot's of resources, but you will not suffer with every Smule update. 

 

Link to comment
Share on other sites

Hauritzius
On 2/12/2021 at 8:59 PM, Gwapo_Erik said:

//Maybe usefull for someone, but here you go a perfect way to use Smule's own code to decode the media url and download a performance.

//tests
//video
//await downloadPerformance('https://www.smule.com/recording/ava-max-sweet-but-psycho/1998769355_3504039030');
//await downloadPerformance('1998769355_3504039030');

//audio
//await downloadPerformance('https://www.smule.com/recording/paper-hearts-guitar-acoustic/823670826_3905415470');
//await downloadPerformance('823670826_3905415470');


//go to smule, copy and paste in console
async function downloadPerformance(urlOrKey) {
    await init();

    var pKey = urlOrKey.split('/').pop();
    var r = await (await fetch(`/p/${pKey}/json`)).json();
    var mediaUrl = r.type == 'video' ? r.video_media_url : r.media_url;
    var decodedMediaUrl = decodeMediaUrl(mediaUrl);

    window.open(decodedMediaUrl, '_blank');
}

async function init() {
    var resolveDecodeMediaUrlFn = async () => {
        return new Promise((resolve, reject) => {
            var resolveDecode = (version, modules) => {
                var decodeMediaUrl;
                for (let id in modules) {
                    let module = modules[id];
                    let exports = module.exports;
                    if (!exports || typeof exports != 'object' || exports == window) continue;

                    if (version == 'v4') {
                        for (let p in exports) {
                            let pv = exports[p];
                            if (pv && typeof pv == 'string' && pv == 'X-CSRF-Token') {
                                if (typeof decodeMediaUrl != 'undefined') continue;

                                for (var p2 in exports) {
                                    if (typeof exports[p2] != 'function') continue;
                                    var fn = exports[p2];
                                    if (fn.toString().indexOf('Failed to decode URL') == -1) continue;
                                    resolve((url) => { return fn(url); });
                                    return;
                                }
                            }
                        }
                    } else if (version == 'v3') {
                        if (!exports.hasOwnProperty('__esModule')) continue;

                        if (exports.hasOwnProperty('CSRF_TOKEN_HEADER')) {
                            resolve((url) => { return exports['processRecording'](url); });
                            return;
                        }
                    }
                }

                throw new Error('decode not found');
            }

            var wp = window.webpackJsonp;
            var mId = 'dummy-' + new Date().getTime();
            if (typeof wp === 'function') {
                wp([], { [mId]: function () { var require = arguments[2]; resolveDecode("v3", require.c); } }, [mId]);
            } else {
                wp.push([[mId], { [mId]: function () { var require = arguments[2]; resolveDecode("v4", require.c); } }, [[mId]]]);
            }
        });
    };

    if (!window.decodeMediaUrl) {
        var r = await resolveDecodeMediaUrlFn();
        window.decodeMediaUrl = r;
    }

    return window.decodeMediaUrl;
}

 

That's a neat function indeed, but it does rely on being executed in the context of a browser while on smule.com as far as I can tell? I did test it though, and it works perfectly. Pretty impressive reverse engineering!

Link to comment
Share on other sites

Hauritzius
On 2/15/2021 at 3:12 AM, _Slynn_ said:

I'm trying to build a telegram bot. and the only struggle I am having is extracting that hidden download link so I can send it back in a message.

I'm using Python

I was contemplating making a Telegram bot for this as well actually, but I always wanted to write metadata to the downloaded file so it is possible to actually figure out what song you've downloaded.

The app I've been working on is now in internal testing, though only for Android. I don't think it will be possible to publish an iOS app with this functionality as it probably breaches some license or user agreement. 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

 Share