GreySec Forums

Full Version: sndcld - soundcloud mp3 downloader
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
Been a while, GS. Many may not remember me, and many may not recognize me, but howdy.

This is a tool I wrote a bit ago, and have been maintaining, and I figured GreySec is a perfect place for me to post it.

It's written to be more like a library, but by default it can function as a standalone tool. Written in C++.

Compilation requires json11 and curlpp.

Code:
[color=#000000]#include <stdio.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <regex>

#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
#include "json11/json11.hpp"

// HERE BE DRAGONS

namespace sndcld_util {

   const std::string user_agent = "User-Agent: Mozilla/5.0";

   std::string _replaceAll(std::string string, std::string substring, std::string replacement, unsigned int index) {

       if (string.find(substring, index) == std::string::npos) {
           return string;
       } else {
           return _replaceAll(
               string.replace(
                   string.find(substring, index),
                   replacement.length(),
                   replacement
               ),
               substring,
               replacement,
               string.find(
                   substring, index
               )
           );
       }

   }

   std::string replaceAll(std::string string, std::string substring, std::string replacement) {

       return _replaceAll(
           string, substring, replacement, 0
       );

   }

   std::string GET(std::string url, std::string output_path) {
       curlpp::Cleanup cleaner;
       curlpp::Easy request;

       request.setOpt(new curlpp::options::Url(url));

       std::list<std::string> headers;
       headers.push_back(user_agent);

       request.setOpt(new curlpp::options::HttpHeader(headers));

       if (output_path == "") {

           std::stringstream data;
           data << request;

           return data.str();

       } else {

           std::ofstream file;
           file.open(output_path);
           file << request;
           file.close();

           return output_path;

       }

       return ""; // Never reached

   }

}

namespace sndcld {

   std::string client_id;
   long int appversion;

   void set_params(std::string cid, long int av) {
       client_id = cid;
       appversion = av;
       // The way those lines aligned is so satisfying
   }

   namespace track_id_methods {

       long int fallback_1(std::string url) {

           return -1; // -1 means it can't be found, even after any fallbacks.

       }

       long int main(std::string url) {

           const std::string response = sndcld_util::GET(url, "");

           std::regex json("\"uri\":\"https://api.soundcloud.com/tracks/(\\d+)\"");
           std::smatch matches;

           if (std::regex_search(response, matches, json)) {
               long int id = std::stoi(matches[1]);
               return id;
           } else {
               return fallback_1(url);
           }

       }

   } // End track id methods

   long int get_track_id(std::string url) {

       return track_id_methods::main(url);

   }

   std::string get_playlist(long int track_id) {

       if (track_id == -1)
           return "";

       const std::string streams_url = "https://api.soundcloud.com/i1/tracks/" + std::to_string(track_id) +
           "/streams?client_id=" + client_id + "&app_version=" + std::to_string(appversion);

       const std::string response = sndcld_util::GET(streams_url, "");
       std::string ERROR = "";
       const json11::Json JSON = json11::Json::parse(response, ERROR);

       if (ERROR != "")
           return "";

       const std::string playlist_url = JSON["hls_mp3_128_url"].string_value();
       const std::string playlist = sndcld_util::GET(playlist_url, "");

       return playlist;

   }

   std::string get_malformed_url(std::string playlist_str) {

       if (playlist_str == "")
           return "";

       std::stringstream playlist(playlist_str);

       std::vector<std::string> lines;
       std::string line;

       while (playlist >> line)
           lines.push_back(line);

       std::stringstream url(

           sndcld_util::replaceAll(
               lines.at(lines.size() - 2),
               "/", "\n"
           )

       );

       std::vector<std::string> url_parts;
       std::string url_part;

       while (url >> url_part)
           url_parts.push_back(url_part);

       std::string malformed_url = url_parts.at(0) + "//" + url_parts.at(1) + "/" + url_parts.at(2) + "/0/" + url_parts.at(4) + "/" + url_parts.at(5);

       return malformed_url;

   }

} // End sndcld

void usage(void) {

   printf("%s\n", "sndcld v3 by Sweets");
   printf("%s\n", " Usage: sndcld [artist] [title] [output path]");
   printf("%s\n", " Example: sndcld iameden r-r ~/Music/eden-rock-and-roll.mp3");
   printf("%s\n", " URL for song in example: https://soundcloud.com/iameden/r-r");

}

int main(int argc, char **argv) {

   if (argv[1] == NULL || argv[2] == NULL || argv[3] == NULL || argv[4] != NULL) {
       usage();
       return 1;
   }

   std::string artist(argv[1]);
   std::string title(argv[2]);
   std::string output_path(argv[3]);
   
   printf("%s\n", " [❤] sndcld v3 by Sweets");

   std::string url = "https://soundcloud.com/" + artist + "/" + title;

   printf("%s\n", " [❤] Setting client_id and appversion.");
   sndcld::set_params("WKcQQdEZw7Oi01KqtHWxeVSxNyRzgT8M", 1505226596);
   printf("%s\n", " [❤] Getting track_id.");

   try {
       long int track_id = sndcld::get_track_id(url);
       printf("%s\n", " [❤] Got track_id.");

       printf("%s\n", " [❤] Getting playlist data.");
       std::string playlist = sndcld::get_playlist(track_id);
       printf("%s\n", " [❤] Got playlist data.");

       printf("%s\n", " [❤] Creating malformed URL.");
       std::string mp3url = sndcld::get_malformed_url(playlist);
       printf("%s\n", " [❤] Malformed URL created.");

       printf("%s\n", " [❤] Downloading song.");
       sndcld_util::GET(mp3url, output_path);
       printf("%s\n", " [❤] Sond downloaded.");

       return 0;

   } catch (curlpp::LibcurlRuntimeError) {
       printf("%s\n", " [(' ^ ';)] An error occured (curl).");

       return 1;
   }

}[/color]

If you copy and paste the code from GS, compilation is done via
Code:
g++ ./sndcld.cpp -lcurlpp -lcurl -ljson11 -o ./sndcld.o
.

You can also clone my git repo, which includes a cmake file.

Code:
git clone https://github.com/Sweets/sndcld-dl.git

Usage is a bit hard to explain, so here's an example.

If I wanted https://soundcloud.com/iameden/r-r, I'd use
Code:
$ sndcld iameden r-r /tmp/output.mp3
.

It only supports song pages alone, so albums and such won't work. They can, though, with some effort, however.
Thanks for the share dude, actually quite useful.
I am new to c++ and I am just being curious... reading stack overflow is a bit of a mess.
Can you use different namespaces at the same time?

So that in this case you don't have to put std:: everywhere.
Welcome back! Sure remember you Smile For the ones who don't, this is one of the guys who helped made this theme a reality! Smile

Anyway, this is a cool tool! Do you know if there's any way to use this to progamatically extract track IDs from song links? Doing it manually seems kind of iffy to me.
(10-17-2017, 03:15 PM)enmafia2 Wrote: [ -> ]I am new to c++ and I am just being curious... reading stack overflow is a bit of a mess.
Can you use different namespaces at the same time?

So that in this case you don't have to put std:: everywhere.

Yes, you can, I generally prefer not to, and as do most C++ programmers. In short, using namespaces is fine, but what can happen is, when using two or more namespaces at once, the compiler can't distinguish between which method to call if methods that belong to two different namespaces share a name.

Strictly speaking you can, but it's very bad to do by most C++ programmers standards, and even my own.

By the way, here's some psuedocode to further illustrate this.

Code:
namespace ABC
    void out()

namespace XYZ
    std::string out

using namespace ABC
using namespace XYZ

out(); // This is where the compiler gets confused

I do believe that most compilers will error out, though I could be wrong, and in the event I'm not, you don't really know what compilers a user may use. I personally use g++, but that doesn't necessarily mean everyone else will, and it'd be bad to assume that.

(10-17-2017, 09:51 PM)Insider Wrote: [ -> ]Anyway, this is a cool tool! Do you know if there's any way to use this to progamatically extract track IDs from song links? Doing it manually seems kind of iffy to me.

Elaborate on that a bit more. You can get one from the source of the page by sending a GET, but if that's what you meant by `manually`, then unfortunately, you cannot get them from the URL alone.
(10-19-2017, 07:26 AM)Sweets Wrote: [ -> ]Elaborate on that a bit more. You can get one from the source of the page by sending a GET, but if that's what you meant by `manually`, then unfortunately, you cannot get them from the URL alone.

Well I was working on a [bb] code for a local MyBB installation this other day. Wanted to play around and try implement a soundcloud tag for this. But the problem is that you can't just embed the url for the track. You need the track ID. I just wish there were some API that I could use to fetch it automatically.

The manual way right now is to click "embed" under the track. Look for the html code. Copy it. And then look through it for the track ID url. Copy that and insert into [bb] code.

It seems kind of pointless to have a [bb] code for soundcloud at that point. And just easier to send a link to the track url.

I was just thinking to myself... I wonder if there's a way to fetch the track ID from the song url programatically. For example set up a php script somewhere that lets you convert track url to track ID url.
(10-19-2017, 07:26 AM)Sweets Wrote: [ -> ]-snip-


Okay I got you, thanks for explaining.
I have another question tho... I'm learning c++ and i'm using CodeBlocks (http://www.codeblocks.org), is there any better alternative out there?
It was the first one I found and it works, but maybe there are any other alternatives Confusedhroug:
(10-21-2017, 03:49 PM)enmafia2 Wrote: [ -> ]
(10-19-2017, 07:26 AM)Sweets Wrote: [ -> ]-snip-


Okay I got you, thanks for explaining.
I have another question tho... I'm learning c++ and i'm using CodeBlocks (http://www.codeblocks.org), is there any better alternative out there?
It was the first one I found and it works, but maybe there are any other alternatives Confusedhroug:

What operating system are you on?

In my experience:

If I'm on Windows, I prefer visual studio for this. I believe there's a free version: https://www.visualstudio.com/downloads/

For linux: Use whatever editor you like. Sublime or whatever.

Fire-up terminal and compile with gcc when you're ready :p

Otherwise, Kdevelop can be something: https://www.kdevelop.org/
(10-21-2017, 03:49 PM)enmafia2 Wrote: [ -> ]
(10-19-2017, 07:26 AM)Sweets Wrote: [ -> ]-snip-


Okay I got you, thanks for explaining.
I have another question tho... I'm learning c++ and i'm using CodeBlocks (http://www.codeblocks.org), is there any better alternative out there?
It was the first one I found and it works, but maybe there are any other alternatives Confusedhroug:

This is very much opinionated.

Personally, I write everything in vim, including C++ programs, and just compile with g++ when I'm done. `wendy` by z3bra is very useful in this case, as you can make g++ recompile the binary everytime you save the file, and that's a good workflow in my mind that I like to follow.

You, however, may not like a workflow like that. You may like to use Eclipse with C++ plugin, or Eclipse C++. It can compile your binaries, too.

There are many options for editors, such as, but not limited to...
  • vi/vim
  • emacs
  • visualstudio (can compile your code)
  • Eclipse C++/Eclipse + C++ plugin (can compile)
  • Sublime (can compile with a plugin)
  • Atom (can compile with a plugin)
  • nano
  • codeblocks
  • kdevelop
  • Notepad/Notepad++ for Windows

The list goes on, almost literally infinitely.
I have a few words of advice.

Stop passing std::string by value.
Passing std::string by value won't always yield operations that you intend. Sure, there are often lots of scenarios for optimisation but you can't rely on that when imposing intentions. At the most basic of levels, passing std::string by value can yield copy-elision via rvalues (from conversion construction from a string literal, for example). You should assume it will invoke a copy constructor for lvalue std::strings (despite the few more specific scenarios where optimisation can be achieved).
I often stick to passing std::string by const reference as I usually intend for them to be immutable. If I intended for them to be mutable, I'd effectively use a non-const reference (in reality I'd use a pointer to non-const std::string since I follow Google's conventions of avoiding the syntactic indirection provided by non-const references).
Passing by const std::string& is probably the most common way people pass std::strings that they intend to be immutable since const references can bind to rvalues (again, you can pass string literals to parameters accepting a const reference to std::string because the implicit conversion construction from a const char* will yield an rvalue that can bind to the const reference). As I said though, depends on your intentions.

Code:
if (output_path == "") {
This is what std::string's empty() method is for.

Code:
return "";
Things like this can be sweetened by just returning a default construction - which will yield an empty std::string - by writing return {};

Code:
std::ofstream file;
file.open(output_path);
Things like this can fail for various reasons that you should account for with error checking.
I'd do something like this:
Code:
if(auto ofs = std::ofstream(output_path))
{
   // use ofs
}
This is direct construction using the path. This is really useful as it will limit the scope of the variable to only where it's valid. The bool() overload (it was void* before C++11 iirc - doesn't really matter) will check its state is valid. And since ofs is now local to the if statement, it means that RAII will call its deconstructor at the end of the if statement block (thus closing the stream implicitly).

Code:
printf("%s\n", "sndcld v3 by Sweets");
You may as well just just puts() or just stream to std::cout (this is C++ after all). If you hadn't used format specifiers and had the newline char on the end of the string literals you're outputting, a decent compiler like GCC would optimise your call to printf to a call to puts. I'd also argue that what you've done is largely overcautious and unnecessary.

Code:
if (argv[1] == NULL || argv[2] == NULL || argv[3] == NULL || argv[4] != NULL) {
I recommend just comparing argc to some integral value since argv[argc] is guaranteed to be NULL, argc is just convenient. If you need additional input validation, do it after you've verified argument count as your first line of defence against invalid input.

Code:
} catch (curlpp::LibcurlRuntimeError) {
I know your exception itself is unused but you should note that it's better practice to catch by reference (usually const reference as the standard exception interface method what() is const-correct).

Assuming there's nothing after that try-catch, the return 0; is implicit at the end of main's control flow.

I also think the namespaces have done little for organisation in reality. They're good practice though, I suppose. 

Good job.

EDIT: I almost forgot since I casted off the notion of you comparing argv elements to NULL (0). You should use nullptr in general. Could also be useful to note that you can often avoid comparing to NULL and nullptr by relying on the fact that any value >0 is true.

(10-19-2017, 07:26 AM)Sweets Wrote: [ -> ]Yes, you can, I generally prefer not to, and as do most C++ programmers. In short, using namespaces is fine, but what can happen is, when using two or more namespaces at once, the compiler can't distinguish between which method to call if methods that belong to two different namespaces share a name.

You should probably generalise to naming (identifier) conflicts.

"methods" is terminology most commonly associated to functions encapsulated in a class (where this is an implicit first parameter).
Pages: 1 2