Base Upload

This commit is contained in:
Maddox Werts 2024-06-09 15:49:00 -04:00
parent 6f5627e5b1
commit cb7c0a0a73
8 changed files with 504 additions and 0 deletions

4
.gitignore vendored
View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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")
}