Partial commit
This commit is contained in:
parent
26f033429d
commit
2cb757f914
18
config.gcfg
18
config.gcfg
|
@ -56,6 +56,24 @@ LowestVolume = 0.01
|
||||||
HighestVolume = 0.8
|
HighestVolume = 0.8
|
||||||
|
|
||||||
|
|
||||||
|
[Web]
|
||||||
|
|
||||||
|
# Enable web browser control
|
||||||
|
# DEFAULT VALUE: true
|
||||||
|
WebBrowser: true
|
||||||
|
|
||||||
|
# Port for web browser, 80 provides links with no port
|
||||||
|
# DEFAULT VALUE: 9563
|
||||||
|
WebPort: 9563
|
||||||
|
|
||||||
|
# Web address to provide links for
|
||||||
|
# DEFAULT VALUE: http://{{IP}}:{{PORT}}/
|
||||||
|
WebAddress: http://{{IP}}:{{PORT}}/
|
||||||
|
|
||||||
|
# Can users have their own web page
|
||||||
|
# DEFAULT VALUE: false
|
||||||
|
CustomWebPage: false
|
||||||
|
|
||||||
[Aliases]
|
[Aliases]
|
||||||
|
|
||||||
# Alias used for add command
|
# Alias used for add command
|
||||||
|
|
75
index.html
75
index.html
|
@ -1,38 +1,49 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="ISO-8859-1">
|
<meta charset="ISO-8859-1">
|
||||||
<title>{{.User}} - mumbledj</title>
|
<title>{{.User}} - mumbledj</title>
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
|
<script
|
||||||
<script type="text/javascript">
|
src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
|
||||||
function api(type) {
|
<script type="text/javascript">
|
||||||
return "http://{{.Site}}/" + type + "?token={{.Token}}";
|
function onLoad() {
|
||||||
}
|
window.setInterval(function() {
|
||||||
|
// Get the song queue
|
||||||
|
}, 6000);
|
||||||
|
}
|
||||||
|
|
||||||
function addURL() {
|
function setAPI(type, val) {
|
||||||
var url = $("#textbox");
|
$.ajax({
|
||||||
$.ajax(api("add") + "&value=" + url.attr("value"));
|
url : "http://{{.Site}}/api/" + type + "?token={{.Token}}"
|
||||||
url.attr("value", "");
|
+ "&value=" + val,
|
||||||
}
|
complete : apiComplete,
|
||||||
|
cache : false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function volume() {
|
function apiCompete(jqXHR, textStatus) {
|
||||||
var volume = $("#textbox");
|
alert(textStatus);
|
||||||
$.ajax(api("volume") + "&value=" + volume.attr("value"));
|
}
|
||||||
volume.attr("value", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function skip(val) {
|
function txtBox(type) {
|
||||||
$.ajax(api("skip") + "&value=" + val);
|
var txt = $("#textbox");
|
||||||
}
|
api(type, txt.attr("value"));
|
||||||
|
txt.attr("value", "");
|
||||||
</script>
|
}
|
||||||
</head>
|
</script>
|
||||||
<body>
|
</head>
|
||||||
<h1>Add Song Form</h1>
|
<body onload="onLoad();">
|
||||||
<input id="textbox" type="text"/>
|
<h1>Add Song Form</h1>
|
||||||
<input id="add" type="button" value="Add Song" onclick="addURL()"/>
|
<input id="textbox" type="text" />
|
||||||
<input id="volume" type="button" value="Set Volume" onclick="volume()"/>
|
<input id="add" type="button" value="Add Song"
|
||||||
<input id="skipSong" type="button" value="Skip Current Song" onclick="skip('false')"/>
|
onclick="setAPI('add', $('#textbox').attr('value'))" />
|
||||||
<input id="skipPlaylist" type="button" value="Skip Current Playlist" onclick="skip('true')"/>
|
<input id="volume" type="button" value="Set Volume"
|
||||||
</body>
|
onclick="setAPI('volume', $('#textbox').attr('value'))" />
|
||||||
|
<input id="skipSong" type="button" value="Skip Current Song"
|
||||||
|
onclick="setAPI('skip', false)" />
|
||||||
|
<input id="skipPlaylist" type="button" value="Skip Current Playlist"
|
||||||
|
onclick="setAPI('skip', true)" />
|
||||||
|
<br />
|
||||||
|
<textarea id="status" rows="10" cols="30"></textarea>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
2
main.go
2
main.go
|
@ -142,12 +142,14 @@ func PerformStartupChecks() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prints out messages only if verbose flag is true
|
||||||
func Verbose(msg string) {
|
func Verbose(msg string) {
|
||||||
if dj.verbose {
|
if dj.verbose {
|
||||||
fmt.Printf(msg + "\n")
|
fmt.Printf(msg + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks to see if an object is nil
|
||||||
func isNil(a interface{}) bool {
|
func isNil(a interface{}) bool {
|
||||||
defer func() { recover() }()
|
defer func() { recover() }()
|
||||||
return a == nil || reflect.ValueOf(a).IsNil()
|
return a == nil || reflect.ValueOf(a).IsNil()
|
||||||
|
|
96
service_soundcloud.go
Normal file
96
service_soundcloud.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/jsonq"
|
||||||
|
"github.com/layeh/gumble/gumble"
|
||||||
|
"github.com/layeh/gumble/gumble_ffmpeg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regular expressions for soundcloud urls
|
||||||
|
var soundcloudSongPattern = `https?:\/\/(www)?\.soundcloud\.com\/([\w-]+)\/([\w-]+)`
|
||||||
|
var soundcloudPlaylistPattern = `https?:\/\/(www)?\.soundcloud\.com\/([\w-]+)\/sets\/([\w-]+)`
|
||||||
|
|
||||||
|
// ------
|
||||||
|
// TYPES
|
||||||
|
// ------
|
||||||
|
|
||||||
|
// YouTube implements the Service interface
|
||||||
|
type SoundCloud struct{}
|
||||||
|
|
||||||
|
// YouTubeSong holds the metadata for a song extracted from a YouTube video.
|
||||||
|
type SoundCloudSong struct {
|
||||||
|
submitter string
|
||||||
|
title string
|
||||||
|
id string
|
||||||
|
offset int
|
||||||
|
filename string
|
||||||
|
duration string
|
||||||
|
thumbnail string
|
||||||
|
skippers []string
|
||||||
|
playlist Playlist
|
||||||
|
dontSkip bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// YouTubePlaylist holds the metadata for a YouTube playlist.
|
||||||
|
type SoundCloudPlaylist struct {
|
||||||
|
id string
|
||||||
|
title string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------
|
||||||
|
// SOUNDCLOUD SERVICE
|
||||||
|
// ------------------
|
||||||
|
|
||||||
|
// Name of the service
|
||||||
|
func (sc SoundCloud) ServiceName() string {
|
||||||
|
return "SoundCloud"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks to see if service will accept URL
|
||||||
|
func (sc SoundCloud) URLRegex(url string) bool {
|
||||||
|
return RegexpFromURL(url, []string{soundcloudSongPattern, soundcloudPlaylistPattern}) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates the requested song/playlist and adds to the queue
|
||||||
|
func (sc SoundCloud) NewRequest(user *gumble.User, url string) (string, error) {
|
||||||
|
var apiResponse *jsonq.JsonQuery
|
||||||
|
var err error
|
||||||
|
url := fmt.Sprintf("http://api.soundcloud.com/resolve?url=%s&client_id=%s", url, os.Getenv("SOUNDCLOUD_API_KEY"))
|
||||||
|
if apiResponse, err = PerformGetRequest(url); err != nil {
|
||||||
|
return nil, errors.New(INVALID_API_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
title, _ := apiResponse.String("title")
|
||||||
|
tracks, err := apiResponse.ArrayOfObjects("tracks")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if re.MatchString(url) {
|
||||||
|
// PLAYLIST
|
||||||
|
if dj.HasPermission(user.Name, dj.conf.Permissions.AdminAddPlaylists) {
|
||||||
|
playlist, err := sc.NewPlaylist(user.Name, url)
|
||||||
|
return playlist.Title(), err
|
||||||
|
} else {
|
||||||
|
return "", errors.New("NO_PLAYLIST_PERMISSION")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// SONG
|
||||||
|
song, err := sc.NewSong(user.Name, url, nil)
|
||||||
|
return song.Title(), err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,7 +110,7 @@ func (yt YouTube) NewSong(user, id, offset string, playlist *YouTubePlaylist) (*
|
||||||
var err error
|
var err error
|
||||||
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s",
|
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s",
|
||||||
id, os.Getenv("YOUTUBE_API_KEY"))
|
id, os.Getenv("YOUTUBE_API_KEY"))
|
||||||
if apiResponse, err = yt.PerformGetRequest(url); err != nil {
|
if apiResponse, err = PerformGetRequest(url); err != nil {
|
||||||
return nil, errors.New(INVALID_API_KEY)
|
return nil, errors.New(INVALID_API_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ func (yt YouTube) NewPlaylist(user, id string) (*YouTubePlaylist, error) {
|
||||||
// Retrieve title of playlist
|
// Retrieve title of playlist
|
||||||
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/playlists?part=snippet&id=%s&key=%s",
|
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/playlists?part=snippet&id=%s&key=%s",
|
||||||
id, os.Getenv("YOUTUBE_API_KEY"))
|
id, os.Getenv("YOUTUBE_API_KEY"))
|
||||||
if apiResponse, err = yt.PerformGetRequest(url); err != nil {
|
if apiResponse, err = PerformGetRequest(url); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
title, _ := apiResponse.String("items", "0", "snippet", "title")
|
title, _ := apiResponse.String("items", "0", "snippet", "title")
|
||||||
|
@ -219,7 +219,7 @@ func (yt YouTube) NewPlaylist(user, id string) (*YouTubePlaylist, error) {
|
||||||
// Retrieve items in playlist
|
// Retrieve items in playlist
|
||||||
url = fmt.Sprintf("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=%s&key=%s",
|
url = fmt.Sprintf("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=%s&key=%s",
|
||||||
id, os.Getenv("YOUTUBE_API_KEY"))
|
id, os.Getenv("YOUTUBE_API_KEY"))
|
||||||
if apiResponse, err = yt.PerformGetRequest(url); err != nil {
|
if apiResponse, err = PerformGetRequest(url); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
numVideos, _ := apiResponse.Int("pageInfo", "totalResults")
|
numVideos, _ := apiResponse.Int("pageInfo", "totalResults")
|
||||||
|
@ -469,7 +469,7 @@ func (p *YouTubePlaylist) Title() string {
|
||||||
// -----------
|
// -----------
|
||||||
|
|
||||||
// PerformGetRequest does all the grunt work for a YouTube HTTPS GET request.
|
// PerformGetRequest does all the grunt work for a YouTube HTTPS GET request.
|
||||||
func (yt YouTube) PerformGetRequest(url string) (*jsonq.JsonQuery, error) {
|
func PerformGetRequest(url string) (*jsonq.JsonQuery, error) {
|
||||||
jsonString := ""
|
jsonString := ""
|
||||||
|
|
||||||
if response, err := http.Get(url); err == nil {
|
if response, err := http.Get(url); err == nil {
|
||||||
|
@ -482,7 +482,7 @@ func (yt YouTube) PerformGetRequest(url string) (*jsonq.JsonQuery, error) {
|
||||||
if response.StatusCode == 403 {
|
if response.StatusCode == 403 {
|
||||||
return nil, errors.New("Invalid API key supplied.")
|
return nil, errors.New("Invalid API key supplied.")
|
||||||
}
|
}
|
||||||
return nil, errors.New("Invalid YouTube ID supplied.")
|
return nil, errors.New("Invalid ID supplied.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("An error occurred while receiving HTTP GET response.")
|
return nil, errors.New("An error occurred while receiving HTTP GET response.")
|
||||||
|
|
|
@ -77,6 +77,14 @@ func (q *SongQueue) Traverse(visit func(i int, s Song)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets the song at a specific point in the queue
|
||||||
|
func (q *SongQueue) Get(int i) (Song, error) {
|
||||||
|
if q.Len() > i+1 {
|
||||||
|
return q.queue[i], nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("Out of Bounds")
|
||||||
|
}
|
||||||
|
|
||||||
// OnSongFinished event. Deletes Song that just finished playing, then queues the next Song (if exists).
|
// OnSongFinished event. Deletes Song that just finished playing, then queues the next Song (if exists).
|
||||||
func (q *SongQueue) OnSongFinished() {
|
func (q *SongQueue) OnSongFinished() {
|
||||||
resetOffset, _ := time.ParseDuration(fmt.Sprintf("%ds", 0))
|
resetOffset, _ := time.ParseDuration(fmt.Sprintf("%ds", 0))
|
||||||
|
|
62
web.go
62
web.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -28,6 +29,21 @@ type Page struct {
|
||||||
User string
|
User string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type status struct {
|
||||||
|
Error bool
|
||||||
|
ErrorMsg string
|
||||||
|
Queue []SongInfo
|
||||||
|
}
|
||||||
|
type SongInfo struct {
|
||||||
|
TitleID string
|
||||||
|
PlaylistID string
|
||||||
|
Title string
|
||||||
|
Playlist string
|
||||||
|
Submitter string
|
||||||
|
Duration string
|
||||||
|
Thumbnail string
|
||||||
|
}
|
||||||
|
|
||||||
var external_ip = ""
|
var external_ip = ""
|
||||||
|
|
||||||
func NewWebServer(port int) *WebServer {
|
func NewWebServer(port int) *WebServer {
|
||||||
|
@ -41,9 +57,10 @@ func NewWebServer(port int) *WebServer {
|
||||||
|
|
||||||
func (web *WebServer) makeWeb() {
|
func (web *WebServer) makeWeb() {
|
||||||
http.HandleFunc("/", web.homepage)
|
http.HandleFunc("/", web.homepage)
|
||||||
http.HandleFunc("/add", web.add)
|
http.HandleFunc("/api/add", web.add)
|
||||||
http.HandleFunc("/volume", web.volume)
|
http.HandleFunc("/api/volume", web.volume)
|
||||||
http.HandleFunc("/skip", web.skip)
|
http.HandleFunc("/api/skip", web.skip)
|
||||||
|
http.HandleFunc("/api/status", web.status)
|
||||||
http.ListenAndServe(":"+strconv.Itoa(web.port), nil)
|
http.ListenAndServe(":"+strconv.Itoa(web.port), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +69,14 @@ func (web *WebServer) homepage(w http.ResponseWriter, r *http.Request) {
|
||||||
if uname == nil {
|
if uname == nil {
|
||||||
fmt.Fprintf(w, "Invalid Token")
|
fmt.Fprintf(w, "Invalid Token")
|
||||||
} else {
|
} else {
|
||||||
cwd, _ := os.Getwd()
|
var webpage = uname.Name
|
||||||
t, err := template.ParseFiles(filepath.Join(cwd, "./.mumbledj/web/index.html"))
|
|
||||||
|
// Check to see if user has a custom webpage
|
||||||
|
if _, err := os.Stat(fmt.Sprintf("%s/.mumbledj/web/%s.html", dj.homeDir, uname.Name)); os.IsNotExist(err) {
|
||||||
|
webpage = "index"
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := template.ParseFiles(fmt.Sprintf("%s/.mumbledj/songs/%s.html", dj.homeDir, uname.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -103,6 +126,33 @@ func (web *WebServer) skip(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (web *WebServer) status(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var uname = web.token_client[r.FormValue("token")]
|
||||||
|
if uname == nil {
|
||||||
|
fmt.Fprintf(w, string(json.MarshalIndent(&Status{true, "Invalid Token"})))
|
||||||
|
} else {
|
||||||
|
// Generate song queue
|
||||||
|
var songsInQueue [dj.queue.Len()]SongInfo
|
||||||
|
for i := 0; i < dj.queue.Len(); i++ {
|
||||||
|
songItem := dj.queue.Get(i)
|
||||||
|
songs[i] = &SongInfo{
|
||||||
|
TitleID: songItem.ID(),
|
||||||
|
Title: songItem.Title(),
|
||||||
|
Submitter: songItem.Submitter(),
|
||||||
|
Duration: songItem.Duration(),
|
||||||
|
Thumbnail: songItem.Thumbnail(),
|
||||||
|
}
|
||||||
|
if !isNil(songItem.Playlist()) {
|
||||||
|
songs[i].PlaylistID = songItem.Playlist().ID()
|
||||||
|
songs[i].Playlist = songItem.Playlist().Title()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output status
|
||||||
|
fmt.Fprintf(w, string(json.MarshalIndent(&Status{false, "", songsInQueue})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (website *WebServer) GetWebAddress(user *gumble.User) {
|
func (website *WebServer) GetWebAddress(user *gumble.User) {
|
||||||
Verbose("Port number: " + strconv.Itoa(web.port))
|
Verbose("Port number: " + strconv.Itoa(web.port))
|
||||||
if web.client_token[user] != "" {
|
if web.client_token[user] != "" {
|
||||||
|
@ -110,7 +160,7 @@ func (website *WebServer) GetWebAddress(user *gumble.User) {
|
||||||
}
|
}
|
||||||
// dealing with collisions
|
// dealing with collisions
|
||||||
var firstLoop = true
|
var firstLoop = true
|
||||||
for firstLoop || web.token_client[web.client_token[user]] != nil {
|
for firstLoop || web.token_client[web.client_token[user]] != nil || web.client_token[user] == "api" {
|
||||||
web.client_token[user] = randSeq(10)
|
web.client_token[user] = randSeq(10)
|
||||||
firstLoop = false
|
firstLoop = false
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue