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
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
# Program directories
|
||||||
|
.tmp/**
|
||||||
|
out/**
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# 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