diff --git a/.gitignore b/.gitignore
index d995b8f..1f1f18e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,4 @@ vendor
bot
sounds
debug
-
+youtube
diff --git a/client/app/app.js b/client/app/app.js
index bc83130..dd31abd 100644
--- a/client/app/app.js
+++ b/client/app/app.js
@@ -6,12 +6,14 @@ import Wrapper from './Wrapper';
import Home from './pages/Home/Home';
import Soundboard from './pages/Soundboard/Soundboard';
import NotFound from './pages/NotFound/NotFound';
+import Downloader from './pages/Downloader/Downloader';
ReactDOM.render(
+
diff --git a/client/app/components/Navbar/Navbar.component.js b/client/app/components/Navbar/Navbar.component.js
index 6d67912..637b2a7 100644
--- a/client/app/components/Navbar/Navbar.component.js
+++ b/client/app/components/Navbar/Navbar.component.js
@@ -11,6 +11,7 @@ export default class Navbar extends React.Component {
Go Discord Bot
Home
Soundboard
+ Youtube Downloader
);
}
diff --git a/client/app/pages/Downloader/Downloader.js b/client/app/pages/Downloader/Downloader.js
new file mode 100644
index 0000000..f1989fd
--- /dev/null
+++ b/client/app/pages/Downloader/Downloader.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import axios from 'axios';
+
+import './Downloader.scss';
+
+export default class Downloader extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ fileType: "mp3",
+ url: "",
+ message: "",
+ dataLoaded: false,
+ downloadLink: "",
+ downLoadFileName: "",
+ dataLoading: false,
+ };
+ }
+
+ sendRequest() {
+ if (this.state.url === "") {
+ this.setState({
+ message: "Invalid URL",
+ });
+
+ return;
+ }
+
+ this.setState({
+ message: "",
+ url: "",
+ dataLoaded: false,
+ dataLoading: true,
+ });
+
+ axios.get(`/ytdownloader`, {
+ params: {
+ fileType: this.state.fileType,
+ url: this.state.url,
+ }
+ }).then((res) => {
+ this.setState({
+ dataLoaded: true,
+ dataLoading: false,
+ downloadLink: `/public/youtube/${res.data.fileName}`,
+ downLoadFileName: res.data.fileName,
+ });
+ }).catch(() => {
+ this.setState({
+ message: "Internal error.",
+ dataLoading: false,
+ });
+ });
+ }
+
+ render() {
+ return (
+
+
+
+ Youtube to MP3
+
+
+
this.setState({url: event.target.value})}/>
+
+ {/*
+
+
+
*/}
+
+
+
+
+
+
+ {this.state.message !== "" &&
{this.state.message}
}
+ {this.state.dataLoaded &&
{this.state.downLoadFileName}}
+
+
+
+ );
+ }
+}
diff --git a/client/app/pages/Downloader/Downloader.scss b/client/app/pages/Downloader/Downloader.scss
new file mode 100644
index 0000000..9057a83
--- /dev/null
+++ b/client/app/pages/Downloader/Downloader.scss
@@ -0,0 +1,16 @@
+.Downloader {
+ padding: 10px;
+}
+
+.Downloader__input {
+ width: 100%;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.Downloader__button {
+
+ & + & {
+ margin-left: 10px;
+ }
+}
diff --git a/client/app/pages/Soundboard/SoundList.component.js b/client/app/pages/Soundboard/SoundList.component.js
index 7a2df6e..7a84e6b 100644
--- a/client/app/pages/Soundboard/SoundList.component.js
+++ b/client/app/pages/Soundboard/SoundList.component.js
@@ -76,7 +76,7 @@ export default class SoundList extends React.Component {
{this.checkExtension(sound.extension) && this.state.showAudioControls[index] ?
-
: this.handleShowAudio(index)}/> }
diff --git a/client/app/pages/Soundboard/Soundboard.js b/client/app/pages/Soundboard/Soundboard.js
index 3c4b7e2..a787760 100644
--- a/client/app/pages/Soundboard/Soundboard.js
+++ b/client/app/pages/Soundboard/Soundboard.js
@@ -17,7 +17,8 @@ export default class Soundboard extends React.Component {
password: "",
uploaded: false,
uploadError: " ",
- }
+ },
+
self = this;
}
@@ -34,7 +35,7 @@ export default class Soundboard extends React.Component {
};
}
- onDrop(acceptedFiles, rejectedFiles) {
+ onDrop(acceptedFiles) {
if (acceptedFiles.length > 0) {
self.uploadFile(acceptedFiles[0]);
}
@@ -90,7 +91,7 @@ export default class Soundboard extends React.Component {
maxSize={10000000000}
accept={"audio/*"}>
-
- )
+ );
}
}
diff --git a/client/app/pages/Soundboard/Soundboard.scss b/client/app/pages/Soundboard/Soundboard.scss
index d720149..c3d24bd 100644
--- a/client/app/pages/Soundboard/Soundboard.scss
+++ b/client/app/pages/Soundboard/Soundboard.scss
@@ -12,14 +12,9 @@
.Soundboard__input {
display: block;
width: 200px;
- border-radius: 3px;
- border: 1px solid lighten($gray1, 5%);
margin-bottom: 10px;
margin-right: auto;
margin-left: auto;
- padding-left: 5px;
- padding-right: 5px;
- height: 30px;
}
.Dropzone {
@@ -32,7 +27,7 @@
padding: 20px;
margin-right: auto;
margin-left: auto;
- color: lighten($gray1, 15%);
+ color: $lightGray;
width: 400px;
height: 400px;
background-color: $gray2;
@@ -42,4 +37,4 @@
.Dropzone--active {
background-color: $gray3;
box-shadow: 0px 0px 5px 1px $primaryBlue;
-}
\ No newline at end of file
+}
diff --git a/client/app/scss/style.scss b/client/app/scss/style.scss
index 887bf1f..81b9ad8 100644
--- a/client/app/scss/style.scss
+++ b/client/app/scss/style.scss
@@ -9,7 +9,7 @@ a, .link {
cursor: pointer;
&:hover {
- color: darken(white, 10%);
+ color: $white;
}
}
@@ -24,6 +24,37 @@ body {
padding-left: $navbarWidth;
}
+.input {
+ border-radius: 3px;
+ border: 1px solid $lightGray;
+ padding-left: 5px;
+ padding-right: 5px;
+ height: 30px;
+ background: $gray5;
+ color: $white;
+}
+
+.button {
+ border: none;
+ border-radius: 3px;
+ color: $white;
+ background: $lightGray;
+ padding: 10px;
+ cursor: pointer;
+
+ &:hover {
+ background: lighten($lightGray, 5%);
+ }
+}
+
+.button--primary {
+ background: $primaryBlue;
+
+ &:hover {
+ background: lighten($primaryBlue, 2%);
+ }
+}
+
.Card {
background-color: $gray2;
border-radius: 5px;
@@ -51,4 +82,4 @@ body {
& + .column {
margin-left: 10px;
}
-}
\ No newline at end of file
+}
diff --git a/client/app/scss/variables.scss b/client/app/scss/variables.scss
index adce2c6..81da684 100644
--- a/client/app/scss/variables.scss
+++ b/client/app/scss/variables.scss
@@ -3,7 +3,7 @@ $navbarWidth: 200px;
// colors
$primaryBlue: #7289da;
-$white: darken(white, 10%);
+$white: darken(white, 20%);
$gray1: #2e3136;
$gray2: #2a2d32;
@@ -11,3 +11,5 @@ $gray3: #23262a;
$gray4: #2e3136;
$gray5: #282b30;
$gray6: #1e2124;
+
+$lightGray: lighten($gray1, 15%);
diff --git a/readme.md b/readme.md
index 480cf89..941977a 100644
--- a/readme.md
+++ b/readme.md
@@ -19,6 +19,10 @@ NOTE: Currently the binaries in the release package only run on linux. Check the
> -tls, run with auto tls
+## Setting up Youtube downloader
+
+- Install [youtube-dl](https://github.com/rg3/youtube-dl/blob/master/README.md#installation)
+
### NOTE
If you get a permissions error with ffmpeg on mac or linux:
diff --git a/server/webserver/handlers/downloader.go b/server/webserver/handlers/downloader.go
new file mode 100644
index 0000000..4fe12d6
--- /dev/null
+++ b/server/webserver/handlers/downloader.go
@@ -0,0 +1,71 @@
+package handlers
+
+import (
+ "bytes"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "regexp"
+
+ "github.com/mgerb/chi_auth_server/response"
+)
+
+// Downloader -
+func Downloader(w http.ResponseWriter, r *http.Request) {
+ url := r.FormValue("url")
+ fileType := r.FormValue("fileType")
+
+ // create youtube folder if it does not exist
+ if _, err := os.Stat("youtube"); os.IsNotExist(err) {
+ os.Mkdir("youtube", os.ModePerm)
+ }
+
+ // get the video title
+ titleCmd := exec.Command("youtube-dl", "--get-title", url)
+ var titleOut bytes.Buffer
+ titleCmd.Stdout = &titleOut
+
+ err := titleCmd.Run()
+
+ if err != nil {
+ log.Println(err)
+ response.ERR(w, http.StatusInternalServerError, response.DefaultInternalError)
+ return
+ }
+
+ // TODO add video id to tile to not get collisions
+
+ // ------------------------------------------------
+
+ // remove all special characters from title
+ cleanTitle := cleanseTitle(titleOut.String())
+ log.Println(cleanTitle)
+
+ cmd := exec.Command("youtube-dl", "-x", "--audio-format", "mp3", "-o", "./youtube/"+cleanTitle+".%(ext)s", url)
+
+ var out bytes.Buffer
+ cmd.Stdout = &out
+
+ err = cmd.Run()
+
+ if err != nil {
+ log.Println(out.String())
+ log.Println(err)
+ response.ERR(w, http.StatusInternalServerError, response.DefaultInternalError)
+ return
+ }
+
+ response.JSON(w, map[string]interface{}{"fileName": cleanTitle + "." + fileType})
+}
+
+func cleanseTitle(title string) string {
+
+ // Make a Regex to say we only want
+ reg, err := regexp.Compile("[^a-zA-Z0-9]+")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ return reg.ReplaceAllString(title, "")
+}
diff --git a/server/webserver/handlers/upload.go b/server/webserver/handlers/upload.go
index ee61ddb..1cfcbc3 100644
--- a/server/webserver/handlers/upload.go
+++ b/server/webserver/handlers/upload.go
@@ -10,6 +10,7 @@ import (
"github.com/mgerb/go-discord-bot/server/config"
)
+// FileUpload
func FileUpload(w http.ResponseWriter, r *http.Request) {
password := r.FormValue("password")
diff --git a/server/webserver/server.go b/server/webserver/server.go
index 04eacbb..0b34ee1 100644
--- a/server/webserver/server.go
+++ b/server/webserver/server.go
@@ -26,18 +26,21 @@ func getRouter() *chi.Mux {
r.Use(middleware.Logger)
}
- r.Get("/soundlist", handlers.SoundList)
- r.Put("/upload", handlers.FileUpload)
-
workDir, _ := os.Getwd()
FileServer(r, "/static", http.Dir(filepath.Join(workDir, "./dist/static")))
- FileServer(r, "/sounds", http.Dir(filepath.Join(workDir, "./sounds")))
+ FileServer(r, "/public/sounds", http.Dir(filepath.Join(workDir, "./sounds")))
+ FileServer(r, "/public/youtube", http.Dir(filepath.Join(workDir, "./youtube")))
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./dist/index.html")
})
+ // configure end points
+ r.Get("/soundlist", handlers.SoundList)
+ r.Put("/upload", handlers.FileUpload)
+ r.Get("/ytdownloader", handlers.Downloader)
+
return r
}