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
|
||||
|
||||
|
||||
[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]
|
||||
|
||||
# Alias used for add command
|
||||
|
|
65
index.html
65
index.html
|
@ -1,38 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="ISO-8859-1">
|
||||
<title>{{.User}} - mumbledj</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
function api(type) {
|
||||
return "http://{{.Site}}/" + type + "?token={{.Token}}";
|
||||
<head>
|
||||
<meta charset="ISO-8859-1">
|
||||
<title>{{.User}} - mumbledj</title>
|
||||
<script
|
||||
src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
function onLoad() {
|
||||
window.setInterval(function() {
|
||||
// Get the song queue
|
||||
}, 6000);
|
||||
}
|
||||
|
||||
function addURL() {
|
||||
var url = $("#textbox");
|
||||
$.ajax(api("add") + "&value=" + url.attr("value"));
|
||||
url.attr("value", "");
|
||||
function setAPI(type, val) {
|
||||
$.ajax({
|
||||
url : "http://{{.Site}}/api/" + type + "?token={{.Token}}"
|
||||
+ "&value=" + val,
|
||||
complete : apiComplete,
|
||||
cache : false
|
||||
});
|
||||
}
|
||||
|
||||
function volume() {
|
||||
var volume = $("#textbox");
|
||||
$.ajax(api("volume") + "&value=" + volume.attr("value"));
|
||||
volume.attr("value", "");
|
||||
function apiCompete(jqXHR, textStatus) {
|
||||
alert(textStatus);
|
||||
}
|
||||
|
||||
function skip(val) {
|
||||
$.ajax(api("skip") + "&value=" + val);
|
||||
function txtBox(type) {
|
||||
var txt = $("#textbox");
|
||||
api(type, txt.attr("value"));
|
||||
txt.attr("value", "");
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</script>
|
||||
</head>
|
||||
<body onload="onLoad();">
|
||||
<h1>Add Song Form</h1>
|
||||
<input id="textbox" type="text"/>
|
||||
<input id="add" type="button" value="Add Song" onclick="addURL()"/>
|
||||
<input id="volume" type="button" value="Set Volume" onclick="volume()"/>
|
||||
<input id="skipSong" type="button" value="Skip Current Song" onclick="skip('false')"/>
|
||||
<input id="skipPlaylist" type="button" value="Skip Current Playlist" onclick="skip('true')"/>
|
||||
</body>
|
||||
<input id="textbox" type="text" />
|
||||
<input id="add" type="button" value="Add Song"
|
||||
onclick="setAPI('add', $('#textbox').attr('value'))" />
|
||||
<input id="volume" type="button" value="Set Volume"
|
||||
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>
|
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) {
|
||||
if dj.verbose {
|
||||
fmt.Printf(msg + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Checks to see if an object is nil
|
||||
func isNil(a interface{}) bool {
|
||||
defer func() { recover() }()
|
||||
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
|
||||
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s",
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -206,7 +206,7 @@ func (yt YouTube) NewPlaylist(user, id string) (*YouTubePlaylist, error) {
|
|||
// Retrieve title of playlist
|
||||
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/playlists?part=snippet&id=%s&key=%s",
|
||||
id, os.Getenv("YOUTUBE_API_KEY"))
|
||||
if apiResponse, err = yt.PerformGetRequest(url); err != nil {
|
||||
if apiResponse, err = PerformGetRequest(url); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
title, _ := apiResponse.String("items", "0", "snippet", "title")
|
||||
|
@ -219,7 +219,7 @@ func (yt YouTube) NewPlaylist(user, id string) (*YouTubePlaylist, error) {
|
|||
// Retrieve items in playlist
|
||||
url = fmt.Sprintf("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=%s&key=%s",
|
||||
id, os.Getenv("YOUTUBE_API_KEY"))
|
||||
if apiResponse, err = yt.PerformGetRequest(url); err != nil {
|
||||
if apiResponse, err = PerformGetRequest(url); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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.
|
||||
func (yt YouTube) PerformGetRequest(url string) (*jsonq.JsonQuery, error) {
|
||||
func PerformGetRequest(url string) (*jsonq.JsonQuery, error) {
|
||||
jsonString := ""
|
||||
|
||||
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 {
|
||||
return nil, errors.New("Invalid API key supplied.")
|
||||
}
|
||||
return nil, errors.New("Invalid YouTube ID supplied.")
|
||||
return nil, errors.New("Invalid ID supplied.")
|
||||
}
|
||||
} else {
|
||||
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).
|
||||
func (q *SongQueue) OnSongFinished() {
|
||||
resetOffset, _ := time.ParseDuration(fmt.Sprintf("%ds", 0))
|
||||
|
|
62
web.go
62
web.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
|
@ -28,6 +29,21 @@ type Page struct {
|
|||
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 = ""
|
||||
|
||||
func NewWebServer(port int) *WebServer {
|
||||
|
@ -41,9 +57,10 @@ func NewWebServer(port int) *WebServer {
|
|||
|
||||
func (web *WebServer) makeWeb() {
|
||||
http.HandleFunc("/", web.homepage)
|
||||
http.HandleFunc("/add", web.add)
|
||||
http.HandleFunc("/volume", web.volume)
|
||||
http.HandleFunc("/skip", web.skip)
|
||||
http.HandleFunc("/api/add", web.add)
|
||||
http.HandleFunc("/api/volume", web.volume)
|
||||
http.HandleFunc("/api/skip", web.skip)
|
||||
http.HandleFunc("/api/status", web.status)
|
||||
http.ListenAndServe(":"+strconv.Itoa(web.port), nil)
|
||||
}
|
||||
|
||||
|
@ -52,8 +69,14 @@ func (web *WebServer) homepage(w http.ResponseWriter, r *http.Request) {
|
|||
if uname == nil {
|
||||
fmt.Fprintf(w, "Invalid Token")
|
||||
} else {
|
||||
cwd, _ := os.Getwd()
|
||||
t, err := template.ParseFiles(filepath.Join(cwd, "./.mumbledj/web/index.html"))
|
||||
var webpage = uname.Name
|
||||
|
||||
// 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 {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
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) {
|
||||
Verbose("Port number: " + strconv.Itoa(web.port))
|
||||
if web.client_token[user] != "" {
|
||||
|
@ -110,7 +160,7 @@ func (website *WebServer) GetWebAddress(user *gumble.User) {
|
|||
}
|
||||
// dealing with collisions
|
||||
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)
|
||||
firstLoop = false
|
||||
}
|
||||
|
|
Reference in a new issue