forked from AYehia0/soundcloud-dl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- A good structured go project - Used some cool dependencies - Go project, go purr
- Loading branch information
Showing
13 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# SC | ||
|
||
A go project to download sound tracks from soundcloud! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# TODO | ||
|
||
- Before Starting: | ||
|
||
- [x] Learn the basics of go lang. | ||
- Learn working with streams and files | ||
- Learn how to send requests | ||
- Learn how to structure the code in go | ||
- Learn some OOP | ||
- Learn Async and multi-threading | ||
|
||
- Get the client ID ? | ||
- Make a GET request to : `https://soundcloud.com` | ||
- Find the last asset : `https://a-v2.sndcdn.com/assets/*` | ||
- Make a get request to that url and extract the `client_id` | ||
|
||
- Validate client_id ? | ||
- Make a get request to `/me` and check the status response. | ||
|
||
- Get auth_token ? | ||
- Find your OAuth token by visiting SoundCloud after logging in and watching any of the browsers requests to the SoundCloud API, the token will be under the `Authorization` header of any of these requests | ||
|
||
- soundcloud uses OAuth so the Authorization header should be as `OAuth {auth_token}` | ||
- Download a track ? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package soundclouddl | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
|
||
"github.com/AYehia0/soundcloud-dl/internal" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var rootCmd = &cobra.Command{ | ||
Use: "sc <url>", | ||
Short: "Sc is a simple CLI application to download soundcloud tracks", | ||
Long: `A blazingly fast go program to download tracks from soundcloud | ||
using just the URL, with some cool features and beautiful UI. | ||
`, | ||
Args: cobra.ArbitraryArgs, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
// get the URL | ||
// TODO: check cobra docs for a cleaner way to do this | ||
if len(args) < 1 { | ||
if err := cmd.Usage(); err != nil { | ||
log.Fatal(err) | ||
} | ||
return | ||
} | ||
// run the core app | ||
internal.Sc(args) | ||
}, | ||
} | ||
|
||
func Execute() { | ||
// initialize the arg parser variables | ||
InitConfigVars() | ||
|
||
if err := rootCmd.Execute(); err != nil { | ||
fmt.Printf("Something went wrong : %s\n", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package soundclouddl | ||
|
||
var Search bool | ||
|
||
// define flags and handle configuration | ||
func InitConfigVars() { | ||
rootCmd.PersistentFlags().BoolVarP(&Search, "search", "s", false, "Does the track exist ?") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module github.com/AYehia0/soundcloud-dl | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/PuerkitoBio/goquery v1.8.0 | ||
github.com/spf13/cobra v1.6.1 | ||
) | ||
|
||
require ( | ||
github.com/andybalholm/cascadia v1.3.1 // indirect | ||
github.com/inconshreveable/mousetrap v1.0.1 // indirect | ||
github.com/spf13/pflag v1.0.5 // indirect | ||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= | ||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= | ||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= | ||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= | ||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= | ||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= | ||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= | ||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= | ||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package internal | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
|
||
"github.com/AYehia0/soundcloud-dl/pkg/client" | ||
"github.com/AYehia0/soundcloud-dl/pkg/soundcloud" | ||
"github.com/PuerkitoBio/goquery" | ||
) | ||
|
||
func Sc(args []string) { | ||
|
||
url := args[0] | ||
|
||
statusCode, body, err := client.Get(url) | ||
|
||
if err != nil { | ||
log.Fatalf("An Error : %s happended while requesting : %s", err, url) | ||
} | ||
if statusCode != http.StatusOK { | ||
fmt.Println("URL doesn't exist : status not 200.") | ||
return | ||
} | ||
|
||
// conver the bytes array into something we can read, as goquery doesn't accept strings | ||
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(body)) | ||
|
||
// check if the url is valid | ||
if !soundcloud.IsValidUrl(url) { | ||
fmt.Printf("The Url : %s doesn't return a valid track are you sure the track is publicly accessed ?", url) | ||
return | ||
} | ||
|
||
soundData := soundcloud.IsValidTrack(doc) | ||
|
||
if soundData == nil { | ||
fmt.Printf("The Url : %s doesn't return a valid track are you sure the track is publicly accessed ?", url) | ||
return | ||
} | ||
fmt.Println("ALL OK :D") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package main | ||
|
||
import soundclouddl "github.com/AYehia0/soundcloud-dl/cmd/soundcloud-dl" | ||
|
||
func main() { | ||
// call the cmd Run command to execute the whole program | ||
soundclouddl.Execute() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
Modify the http package to allow easy calls | ||
*/ | ||
package client | ||
|
||
import ( | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
) | ||
|
||
type HTTP struct { | ||
client *http.Client | ||
} | ||
|
||
// timeout the http request to a level and sets a proxy is passed | ||
func New(timeout time.Duration, proxy string) (*HTTP, error) { | ||
httpConn := &HTTP{ | ||
client: http.DefaultClient, | ||
} | ||
|
||
if timeout > 0 { | ||
httpConn.client.Timeout = timeout | ||
} | ||
|
||
if proxy != "" { | ||
// parse the url | ||
proxy, err := url.Parse(proxy) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
t := http.DefaultTransport.(*http.Transport) | ||
t.Proxy = func(*http.Request) (*url.URL, error) { | ||
return proxy, nil | ||
} | ||
httpConn.client.Transport = t | ||
} | ||
|
||
return httpConn, nil | ||
} | ||
|
||
// make a GET request and return some info about the request | ||
func Get(url string) (int, []byte, error) { | ||
resp, err := http.Get(url) | ||
|
||
if err != nil { | ||
return -1, nil, err | ||
} | ||
|
||
// read the response body | ||
bodyBytes, err := ioutil.ReadAll(resp.Body) | ||
|
||
defer resp.Body.Close() | ||
|
||
if err != nil { | ||
return resp.StatusCode, nil, err | ||
} | ||
|
||
return resp.StatusCode, bodyBytes, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package soundcloud | ||
|
||
import ( | ||
"encoding/json" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/PuerkitoBio/goquery" | ||
) | ||
|
||
// convert the data map[string]any to well defined struct | ||
// by marshalling then unmarshalling, which is probably isn't the best thing todo. | ||
func formatData(data map[string]any) (*SoundData, error) { | ||
sound := &SoundData{} | ||
jsonBody, _ := json.Marshal(data) | ||
if err := json.Unmarshal(jsonBody, &sound); err != nil { | ||
// do error check | ||
return nil, err | ||
} | ||
// adding the transcodings | ||
// go goes crazy mode lol | ||
transcodings := data["media"].(map[string]interface{})["transcodings"].([]any) | ||
|
||
for _, val := range transcodings { | ||
x := val.(map[string]any) | ||
encoding := Transcode{ | ||
ApiUrl: x["url"].(string), | ||
Quality: x["quality"].(string), | ||
Format: x["format"].(map[string]interface{})["mime_type"].(string), | ||
} | ||
sound.Transcodings = append(sound.Transcodings, encoding) | ||
} | ||
|
||
return sound, nil | ||
} | ||
|
||
// extract some meta data under : window.__sc_hydration | ||
func GetSoundMetaData(doc *goquery.Document) *SoundData { | ||
|
||
page := doc.First().Text() | ||
|
||
// TODO: You can do it in one shot, without splitting the text, but meh | ||
re := regexp.MustCompile(`(?m)^window.__sc_hydration = (.*?);\s*$`) | ||
matches := re.FindAllStringSubmatch(page, 1) | ||
|
||
if len(matches) < 1 { | ||
return nil | ||
} | ||
|
||
var result []map[string]interface{} | ||
data := strings.Split(matches[0][1], "\n")[0] | ||
|
||
json.Unmarshal([]byte(data), &result) | ||
|
||
// extracting the data | ||
// userData := result[6]["data"] | ||
soundData := result[7]["data"].(map[string]any) | ||
|
||
// TODO: return the error here | ||
dataSound, _ := formatData(soundData) | ||
|
||
return dataSound | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package soundcloud | ||
|
||
type Transcode struct { | ||
ApiUrl string | ||
Quality string | ||
Format string | ||
} | ||
|
||
type SoundData struct { | ||
Id int64 `json:"id"` | ||
Title string `json:"title"` | ||
CreatedAt string `json:"created_at"` | ||
Duration int64 `json:"duration"` | ||
Kind string `json:"kind"` | ||
PermalinkUrl string `json:"permalink_url"` | ||
UserId int64 `json:"user_id"` | ||
Transcodings []Transcode | ||
TrackAuth string `json:"track_authorization"` | ||
LikesCount int `json:"likes_count"` | ||
Downloadable bool `json:"downloadable"` | ||
Description string `json:"description,omitempty"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
Check if the URL passed is a valid URL by matching a regex. | ||
*/ | ||
package soundcloud | ||
|
||
import ( | ||
"log" | ||
"regexp" | ||
|
||
"github.com/PuerkitoBio/goquery" | ||
) | ||
|
||
// check if the url is a soundcloud url | ||
func IsValidUrl(url string) bool { | ||
|
||
/* | ||
^ - start of string | ||
(?:https?://)? - an optional http:// or https:// | ||
(?:[^/\s]+\.)* - zero or more repetitions of : | ||
[^/.\s]+ - one or more chars other than /, . and whitespace | ||
\. - a dot | ||
google\.com - an escaped keyword | ||
(?:/[^/\s]+)* - zero or more repetitions of a / and then one or more chars other than / and whitespace chars | ||
/? - an optional / | ||
$ - end of string | ||
*/ | ||
pattern := `^(?:https?://)?(?:[^/.\s]+\.)*soundcloud\.com(?:/[^/\s]+)*/?$` | ||
matched, err := regexp.MatchString(pattern, url) | ||
if err != nil { | ||
log.Fatalf("Something went wrong while parsing the URL : %s", err) | ||
} | ||
|
||
if matched { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
// check if the track exists and open to public | ||
// by checking the html for something unique like : likes, | ||
// the url must be a IsValidUrl first | ||
func IsValidTrack(doc *goquery.Document) *SoundData { | ||
|
||
sound := GetSoundMetaData(doc) | ||
|
||
return sound | ||
} |