Base Upload
This commit is contained in:
parent
6f5627e5b1
commit
cb7c0a0a73
8 changed files with 504 additions and 0 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -15,6 +15,10 @@
|
|||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Program directories
|
||||
.tmp/**
|
||||
out/**
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
|
|
9
go.mod
Normal file
9
go.mod
Normal file
|
@ -0,0 +1,9 @@
|
|||
module SongPuller
|
||||
|
||||
go 1.22.4
|
||||
|
||||
require (
|
||||
github.com/bogem/id3v2 v1.2.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
)
|
8
go.sum
Normal file
8
go.sum
Normal file
|
@ -0,0 +1,8 @@
|
|||
github.com/bogem/id3v2 v1.2.0 h1:hKDF+F1gOgQ5r1QmBCEZUk4MveJbKxCeIDSBU7CQ4oI=
|
||||
github.com/bogem/id3v2 v1.2.0/go.mod h1:t78PK5AQ56Q47kizpYiV6gtjj3jfxlz87oFpty8DYs8=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
101
main.go
Normal file
101
main.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
// Package
|
||||
package main
|
||||
|
||||
// Libraries
|
||||
import (
|
||||
backend "SongPuller/src"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Variables
|
||||
var pages = [2]backend.CLI{}
|
||||
var page int = 0
|
||||
|
||||
// Functions
|
||||
func initPages() {
|
||||
// Holders
|
||||
var cPage backend.CLI
|
||||
|
||||
// Main Menu
|
||||
/// Creating variable
|
||||
cPage.Init("Main Menu", func() {
|
||||
fmt.Println("1) Download a Song")
|
||||
fmt.Println("2) Exit")
|
||||
fmt.Print("Select option [1-2]: ")
|
||||
|
||||
// The scan variable
|
||||
var usrInput string
|
||||
|
||||
// Scanning what the user wants
|
||||
fmt.Scanln(&usrInput)
|
||||
|
||||
// Based on this, what do we do?
|
||||
if usrInput == "1" {
|
||||
page = 1
|
||||
}
|
||||
})
|
||||
/// Append page
|
||||
pages[0] = cPage
|
||||
|
||||
// Song Info
|
||||
/// Creating variable
|
||||
cPage.Init("Song Information", func() {
|
||||
// The scan variable
|
||||
var usrInput string
|
||||
var songURL string
|
||||
|
||||
// Introduction
|
||||
fmt.Println("Before we proceed, Some basic info")
|
||||
fmt.Println("This program is designed to download songs from:")
|
||||
fmt.Print(string(backend.CliYellow))
|
||||
fmt.Println(" - Soundcloud")
|
||||
fmt.Print(string(backend.CliWhite))
|
||||
fmt.Println()
|
||||
fmt.Println("We just need you to give us some very basic information.")
|
||||
fmt.Println()
|
||||
|
||||
// The URL of the site
|
||||
fmt.Println("First, what's the Share URL of your song?")
|
||||
fmt.Print("URL [https://www.soundcloud.com/---]: ")
|
||||
|
||||
// Scanning what the user wants
|
||||
fmt.Scanln(&songURL)
|
||||
fmt.Println()
|
||||
|
||||
// Running the puller
|
||||
var meta backend.SongMetadata
|
||||
meta.Init("soundcloud", songURL)
|
||||
|
||||
// Asking if the song name is correct
|
||||
fmt.Println("Is your song's name: " + string(backend.CliGreen) + meta.Title + string(backend.CliWhite) + "?")
|
||||
fmt.Print("Select option [y/n]: ")
|
||||
|
||||
// Scanning if the user likes what is shown
|
||||
fmt.Scanln(&usrInput)
|
||||
fmt.Println()
|
||||
|
||||
// If it's not the song they want, then appologize!
|
||||
if usrInput != "y" && usrInput != "Y" {
|
||||
fmt.Println()
|
||||
fmt.Println("Sorry about that! If you could please try that again.")
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Switch back to main menu
|
||||
page = 0
|
||||
})
|
||||
/// Append page
|
||||
pages[1] = cPage
|
||||
}
|
||||
|
||||
// Entry Point
|
||||
func main() {
|
||||
// Initilizing pages
|
||||
initPages()
|
||||
|
||||
// Run Loop
|
||||
for backend.Running {
|
||||
// Doing page stuff
|
||||
pages[page].Run()
|
||||
}
|
||||
}
|
49
src/cli.go
Normal file
49
src/cli.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Package
|
||||
package backend
|
||||
|
||||
// Libraries
|
||||
import "fmt"
|
||||
|
||||
// Constants
|
||||
// / Colors
|
||||
const CliRed string = "\033[31m"
|
||||
const CliGreen string = "\033[32m"
|
||||
const CliYellow string = "\033[33m"
|
||||
const CliBlue string = "\033[34m"
|
||||
const CliPurple string = "\033[35m"
|
||||
const CliCyan string = "\033[36m"
|
||||
const CliWhite string = "\033[37m"
|
||||
|
||||
// Variables
|
||||
var Running bool
|
||||
|
||||
// Structs
|
||||
type CLI struct {
|
||||
Title string
|
||||
OnRun func()
|
||||
}
|
||||
|
||||
// Interfaces
|
||||
func (c *CLI) Init(title string, onRun func()) {
|
||||
// Setting variables
|
||||
Running = true
|
||||
c.Title = title
|
||||
c.OnRun = onRun
|
||||
}
|
||||
func (c *CLI) Run() {
|
||||
// Drawing Title
|
||||
fmt.Print(string(CliCyan))
|
||||
fmt.Println(" - " + c.Title + " - ")
|
||||
fmt.Print(string(CliWhite))
|
||||
|
||||
// New line
|
||||
fmt.Println()
|
||||
|
||||
// Running the rest of the draw function
|
||||
c.OnRun()
|
||||
|
||||
// New lines
|
||||
for range 2 {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
62
src/filer.go
Normal file
62
src/filer.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Package
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Libraries
|
||||
|
||||
// Structs
|
||||
type Filer struct {
|
||||
}
|
||||
|
||||
// Interfaces
|
||||
func (f *Filer) DownloadFromURL(url string, path string, filename string) bool {
|
||||
// Get the image from the URL
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Println("FILER: Error fetching image:", err)
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Get the current working directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println("FILER: Error getting current working directory:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Define the destination directory
|
||||
destDir := filepath.Join(cwd, path)
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
err = os.MkdirAll(filepath.Join(path), os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Println("FILER: Error creating directory:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Create a file to store the image
|
||||
file, err := os.Create(filepath.Join(destDir, filename))
|
||||
if err != nil {
|
||||
fmt.Println("FILER: Error creating file:", err)
|
||||
return false
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Copy the image data to the file
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("FILER: Error copying image data:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// It worked!
|
||||
return true
|
||||
}
|
71
src/html.go
Normal file
71
src/html.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
// Package
|
||||
package backend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Libraries
|
||||
|
||||
// Structs
|
||||
type HTML_Document struct {
|
||||
content []byte
|
||||
doc *html.Node
|
||||
}
|
||||
|
||||
// Interfaces
|
||||
func (h *HTML_Document) Init(content []byte) {
|
||||
// Setting the content
|
||||
h.content = content
|
||||
|
||||
// Parsing the HTML
|
||||
var err error
|
||||
h.doc, err = html.Parse(bytes.NewReader(h.content))
|
||||
if err != nil {
|
||||
fmt.Println("HTML: Error parsing HTML")
|
||||
return
|
||||
}
|
||||
}
|
||||
func (h *HTML_Document) FindByID(n *html.Node, id string, tag string) *html.Node {
|
||||
if n.Type == html.ElementNode && n.Data == tag {
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Key == "id" && strings.Contains(attr.Val, id) {
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if node := h.FindByID(c, id, tag); node != nil {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (h *HTML_Document) GetTextContent(node *html.Node) string {
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var textContent string
|
||||
if node.Type == html.TextNode {
|
||||
textContent = node.Data
|
||||
}
|
||||
|
||||
for c := node.FirstChild; c != nil; c = c.NextSibling {
|
||||
textContent += h.GetTextContent(c)
|
||||
}
|
||||
|
||||
return textContent
|
||||
}
|
||||
func (h *HTML_Document) GetElementAttr(n *html.Node, attr string) string {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == attr {
|
||||
return a.Val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
200
src/puller.go
Normal file
200
src/puller.go
Normal file
|
@ -0,0 +1,200 @@
|
|||
// Package
|
||||
package backend
|
||||
|
||||
// Libraries
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bogem/id3v2"
|
||||
)
|
||||
|
||||
// Structs
|
||||
type SongMetadata struct {
|
||||
url string
|
||||
|
||||
Title string
|
||||
author string
|
||||
image string
|
||||
download string
|
||||
}
|
||||
|
||||
// Functions
|
||||
func getCSRFTokenAndCookie(url string) (string, *http.Cookie, error) {
|
||||
client := &http.Client{}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Extract CSRF token from cookies
|
||||
var csrfCookie *http.Cookie
|
||||
for _, cookie := range resp.Cookies() {
|
||||
if cookie.Name == "csrftoken" {
|
||||
csrfCookie = cookie
|
||||
break
|
||||
}
|
||||
}
|
||||
if csrfCookie == nil {
|
||||
return "", nil, fmt.Errorf("CSRF cookie not found")
|
||||
}
|
||||
|
||||
// If the CSRF token is stored in the page content
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
re := regexp.MustCompile(`name="csrfmiddlewaretoken" value="([^"]+)"`)
|
||||
match := re.FindStringSubmatch(string(body))
|
||||
if len(match) > 1 {
|
||||
return match[1], csrfCookie, nil
|
||||
}
|
||||
|
||||
return "", nil, fmt.Errorf("CSRF token not found")
|
||||
}
|
||||
|
||||
func (s *SongMetadata) pullFromSoundCloud() {
|
||||
// Reference variables
|
||||
apiBase := "https://sclouddownloader.net"
|
||||
apiURL := "https://sclouddownloader.net/download-sound-track"
|
||||
|
||||
// Getting CSRF token
|
||||
csrfToken, csrfCookie, err := getCSRFTokenAndCookie(apiBase)
|
||||
if err != nil {
|
||||
fmt.Println("PULLER: Error fetching CSRF token")
|
||||
return
|
||||
}
|
||||
|
||||
// Post data
|
||||
data := url.Values{}
|
||||
data.Set("url", s.url)
|
||||
data.Set("csrfmiddlewaretoken", csrfToken)
|
||||
|
||||
// Creating a POST request
|
||||
req, err := http.NewRequest("POST", apiURL, bytes.NewBufferString(data.Encode()))
|
||||
|
||||
// Was there an error sending a request?
|
||||
if err != nil {
|
||||
fmt.Println("PULLER: Error creating API request")
|
||||
return
|
||||
}
|
||||
|
||||
// Setting the request headers
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("X-CSRFTOKEN", csrfToken)
|
||||
req.Header.Set("Referer", apiURL)
|
||||
req.AddCookie(csrfCookie)
|
||||
|
||||
// Send request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println("PULLER: Error sending request to API")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("PULLER: Error reading response")
|
||||
return
|
||||
}
|
||||
|
||||
// Feeding the body content into an HTML parser
|
||||
var htmldoc HTML_Document
|
||||
htmldoc.Init(body)
|
||||
|
||||
// Now we get specific elements like the title of the song:
|
||||
s.Title = strings.Split(htmldoc.GetTextContent(htmldoc.FindByID(htmldoc.doc, "trackTitle", "p")), ".mp3")[0]
|
||||
s.author = strings.Split(htmldoc.GetTextContent(htmldoc.FindByID(htmldoc.doc, "trackUploader", "p")), "by ")[1]
|
||||
s.download = htmldoc.GetElementAttr(htmldoc.FindByID(htmldoc.doc, "trackLink", "a"), "href")
|
||||
s.image = htmldoc.GetElementAttr(htmldoc.FindByID(htmldoc.doc, "trackThumbnail", "img"), "src")
|
||||
|
||||
// Downloading the song and image
|
||||
var filer Filer
|
||||
|
||||
if !filer.DownloadFromURL(s.image, ".tmp", s.Title+".jpg") || !filer.DownloadFromURL(s.download, "out", s.Title+".mp3") {
|
||||
fmt.Println("PULLER: Exiting process...")
|
||||
return
|
||||
}
|
||||
|
||||
// Apply Metadata
|
||||
s.ApplyMetadata()
|
||||
}
|
||||
|
||||
func (s *SongMetadata) Init(songPlatform string, url string) {
|
||||
// Setting variables
|
||||
s.url = url
|
||||
|
||||
// Doing stuff based on the platform
|
||||
switch songPlatform {
|
||||
case "soundcloud":
|
||||
// Running a Fetch request
|
||||
s.pullFromSoundCloud()
|
||||
}
|
||||
}
|
||||
func (s *SongMetadata) ApplyMetadata() {
|
||||
mp3Path := "out/" + s.Title + ".mp3"
|
||||
imagePath := ".tmp/" + s.Title + ".jpg"
|
||||
title := s.Title
|
||||
author := s.author
|
||||
|
||||
// Open the MP3 file
|
||||
tag, err := id3v2.Open(mp3Path, id3v2.Options{Parse: true})
|
||||
if err != nil {
|
||||
fmt.Println("PULLER: Error opening MP3 file:", err)
|
||||
return
|
||||
}
|
||||
defer tag.Close()
|
||||
|
||||
// Set the title and author
|
||||
tag.SetTitle(title)
|
||||
tag.SetArtist(author)
|
||||
|
||||
// Add the image
|
||||
img, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
fmt.Println("PULLER: Error opening image file:", err)
|
||||
return
|
||||
}
|
||||
defer img.Close()
|
||||
|
||||
imgInfo, err := img.Stat()
|
||||
if err != nil {
|
||||
fmt.Println("PULLER: Error getting image info:", err)
|
||||
return
|
||||
}
|
||||
|
||||
imgData := make([]byte, imgInfo.Size())
|
||||
_, err = img.Read(imgData)
|
||||
if err != nil {
|
||||
fmt.Println("PULLER: Error reading image data:", err)
|
||||
return
|
||||
}
|
||||
|
||||
picFrame := id3v2.PictureFrame{
|
||||
Encoding: id3v2.EncodingUTF8,
|
||||
MimeType: "image/jpeg",
|
||||
PictureType: id3v2.PTFrontCover,
|
||||
Description: "Cover",
|
||||
Picture: imgData,
|
||||
}
|
||||
|
||||
tag.AddAttachedPicture(picFrame)
|
||||
|
||||
// Save the changes
|
||||
if err = tag.Save(); err != nil {
|
||||
fmt.Println("PULLER: Error saving MP3 file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Successfully downloaded song")
|
||||
}
|
Loading…
Reference in a new issue