Partial commit

This commit is contained in:
MichaelOultram 2015-08-02 18:55:51 +01:00
parent 26f033429d
commit 2cb757f914
7 changed files with 228 additions and 43 deletions

View file

@ -55,6 +55,24 @@ LowestVolume = 0.01
# DEFAULT VALUE: 0.8 # DEFAULT VALUE: 0.8
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]

View file

@ -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>

View file

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

View file

@ -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.")

View file

@ -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
View file

@ -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
} }