2022-03-05 10:40:06 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
type API struct {
|
|
|
|
config *Config
|
|
|
|
queue *Queue
|
|
|
|
wg sync.WaitGroup
|
|
|
|
nr *QueueTrackEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewAPI(config *Config, queue *Queue) *API {
|
|
|
|
api := &API{
|
|
|
|
config: config,
|
|
|
|
queue: queue,
|
|
|
|
}
|
|
|
|
|
|
|
|
api.listen()
|
|
|
|
api.handleQueue()
|
|
|
|
|
|
|
|
return api
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *API) Wait() {
|
|
|
|
a.wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func getQueueEntryFromBody(body []byte) *QueueTrackEntry {
|
|
|
|
entry := &QueueTrackEntry{}
|
|
|
|
err := json.Unmarshal(body, &entry.original)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(body, &entry.Metadata)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var val interface{}
|
|
|
|
var strVal string
|
|
|
|
var ok bool
|
|
|
|
if val, ok = entry.original["path"]; ok {
|
|
|
|
if strVal, ok = val.(string); ok {
|
|
|
|
entry.Path = strVal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(entry.Path) > 0 {
|
|
|
|
return entry
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *API) getFallbackTrack() *QueueTrackEntry {
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
m["path"] = a.config.Queue.FallbackPath
|
|
|
|
return &QueueTrackEntry{
|
|
|
|
Path: a.config.Queue.FallbackPath,
|
|
|
|
original: m,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (a *API) getRandomTrack() *QueueTrackEntry {
|
|
|
|
response, err := http.DefaultClient.Get(a.config.Queue.RandomSongApi)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
defer response.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(response.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return getQueueEntryFromBody(body)
|
|
|
|
}
|
|
|
|
func (a *API) setNowRandom(nr *QueueTrackEntry) {
|
|
|
|
a.nr = nr
|
|
|
|
if a.config.Queue.NowRandom != "" {
|
|
|
|
jsonData, _ := json.Marshal(nr.original)
|
|
|
|
response, err := http.DefaultClient.Post(a.config.Queue.NowRandom, "application/json; charset=utf-8", bytes.NewReader(jsonData))
|
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
}
|
|
|
|
if response != nil {
|
|
|
|
defer response.Body.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *API) handleQueue() {
|
|
|
|
a.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer a.wg.Done()
|
|
|
|
defer close(a.queue.QueueEmpty)
|
|
|
|
//TODO: close properly
|
|
|
|
for {
|
|
|
|
if e := a.getRandomTrack(); e != nil {
|
|
|
|
//preload
|
|
|
|
if err := e.Load(); err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
a.setNowRandom(e)
|
|
|
|
a.queue.QueueEmpty <- e
|
|
|
|
} else if e = a.getFallbackTrack(); e != nil {
|
|
|
|
//preload
|
|
|
|
if err := e.Load(); err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
a.setNowRandom(e)
|
|
|
|
a.queue.QueueEmpty <- e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
a.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer a.wg.Done()
|
|
|
|
for np := range a.queue.NowPlaying {
|
|
|
|
jsonData, _ := json.Marshal(np.original)
|
|
|
|
response, err := http.DefaultClient.Post(a.config.Queue.NowPlaying, "application/json; charset=utf-8", bytes.NewReader(jsonData))
|
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
}
|
|
|
|
if response != nil {
|
|
|
|
response.Body.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
//insert first track
|
|
|
|
a.queue.HandleQueue()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *API) listen() {
|
|
|
|
|
|
|
|
type resultResponse struct {
|
|
|
|
Success bool `json:"success"`
|
|
|
|
Reason error `json:"reason"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type queueResultResponse struct {
|
|
|
|
resultResponse
|
|
|
|
QueueId audio.QueueIdentifier `json:"queue_id"`
|
|
|
|
}
|
|
|
|
|
|
|
|
a.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer a.wg.Done()
|
|
|
|
|
|
|
|
server := http.Server{
|
|
|
|
Addr: fmt.Sprintf("%s:%d", a.config.Api.Host, a.config.Api.Port),
|
|
|
|
Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
|
|
writer.Header().Set("Server", "MeteorLight/api")
|
|
|
|
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
|
|
|
|
|
|
pathSegments := strings.Split(request.URL.Path, "/")
|
|
|
|
if len(pathSegments) > 1 {
|
|
|
|
switch pathSegments[1] {
|
|
|
|
case "listeners":
|
|
|
|
jsonData, _ := json.Marshal(a.queue.GetListeners())
|
|
|
|
writer.Write(jsonData)
|
|
|
|
|
|
|
|
return
|
|
|
|
case "np":
|
|
|
|
if e := a.queue.GetNowPlaying(); e != nil {
|
|
|
|
jsonData, _ := json.Marshal(e.original)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
} else {
|
|
|
|
writer.Write([]byte{'{', '}'})
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
case "random":
|
|
|
|
if a.nr != nil {
|
|
|
|
jsonData, _ := json.Marshal(a.nr.original)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
} else {
|
|
|
|
writer.Write([]byte{'{', '}'})
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
case "queue":
|
|
|
|
if len(pathSegments) == 2 {
|
|
|
|
if request.Method != "GET" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var blobs = make([]map[string]interface{}, 0, 1)
|
|
|
|
for _, e := range a.queue.GetQueue() {
|
|
|
|
blobs = append(blobs, e.original)
|
|
|
|
}
|
|
|
|
jsonData, _ := json.Marshal(blobs)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
switch pathSegments[2] {
|
|
|
|
case "head":
|
|
|
|
if request.Method == "POST" {
|
|
|
|
result := queueResultResponse{}
|
|
|
|
if body, err := ioutil.ReadAll(request.Body); err == nil {
|
|
|
|
if e := getQueueEntryFromBody(body); e != nil {
|
|
|
|
if err = a.queue.AddTrack(e, false); err == nil {
|
|
|
|
result.Success = true
|
|
|
|
result.QueueId = e.QueueIdentifier
|
2022-04-20 09:04:53 +00:00
|
|
|
} else {
|
|
|
|
log.Printf("track addition error: \"%s\"", err)
|
2022-03-05 10:40:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, _ := json.Marshal(result)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
|
|
|
|
return
|
|
|
|
} else if request.Method == "DELETE" {
|
|
|
|
result := resultResponse{}
|
|
|
|
if head := a.queue.GetHead(); head != nil {
|
|
|
|
result.Success = a.queue.Remove(head.QueueIdentifier)
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, _ := json.Marshal(result)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
case "tail":
|
|
|
|
if request.Method == "POST" {
|
|
|
|
result := queueResultResponse{}
|
|
|
|
if body, err := ioutil.ReadAll(request.Body); err == nil {
|
|
|
|
if e := getQueueEntryFromBody(body); e != nil {
|
|
|
|
if err = a.queue.AddTrack(e, true); err == nil {
|
|
|
|
result.Success = true
|
|
|
|
result.QueueId = e.QueueIdentifier
|
2022-04-20 09:04:53 +00:00
|
|
|
} else {
|
|
|
|
log.Printf("track addition error: \"%s\"", err)
|
2022-03-05 10:40:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, _ := json.Marshal(result)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
|
|
|
|
return
|
|
|
|
} else if request.Method == "DELETE" {
|
|
|
|
result := resultResponse{}
|
|
|
|
if head := a.queue.GetTail(); head != nil {
|
|
|
|
result.Success = a.queue.Remove(head.QueueIdentifier)
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, _ := json.Marshal(result)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
case "clear":
|
|
|
|
if request.Method != "POST" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
result := resultResponse{}
|
|
|
|
|
|
|
|
for _, e := range a.queue.GetQueue() {
|
|
|
|
a.queue.Remove(e.QueueIdentifier)
|
|
|
|
}
|
|
|
|
result.Success = true
|
|
|
|
|
|
|
|
jsonData, _ := json.Marshal(result)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
return
|
|
|
|
|
|
|
|
default:
|
|
|
|
if request.Method != "POST" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if i, err := strconv.ParseInt(pathSegments[2], 10, 0); err == nil {
|
|
|
|
result := resultResponse{}
|
|
|
|
|
|
|
|
result.Success = a.queue.Remove(audio.QueueIdentifier(i))
|
|
|
|
|
|
|
|
jsonData, _ := json.Marshal(result)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
case "skip":
|
|
|
|
if request.Method != "POST" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
result := resultResponse{}
|
|
|
|
|
|
|
|
result.Success = a.queue.SkipNowPlaying()
|
|
|
|
|
|
|
|
jsonData, _ := json.Marshal(result)
|
|
|
|
writer.Write(jsonData)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
writer.WriteHeader(http.StatusNotFound)
|
|
|
|
writer.Write([]byte{'{', '}'})
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := server.ListenAndServe(); err != nil {
|
|
|
|
log.Panic(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|