Skip to content

Commit de9789a

Browse files
committed
feat: Initial commit
0 parents  commit de9789a

File tree

13 files changed

+1601
-0
lines changed

13 files changed

+1601
-0
lines changed

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
run:
2+
go run .
3+
4+
dev:
5+
air

config.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[spotify]
2+
url = "http://aether:24879"
3+
ws = "aether:24879"
4+
5+
[fallback]
6+
playlist = "spotify:playlist:3vleaMH00xMCNXOWHsqm73"

go.mod

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module github.com/ODDInvictus/aether
2+
3+
go 1.21.1
4+
5+
require (
6+
github.com/bytedance/sonic v1.10.2 // indirect
7+
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
8+
github.com/chenzhuoyu/iasm v0.9.0 // indirect
9+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
10+
github.com/faiface/beep v1.1.0 // indirect
11+
github.com/fatih/color v1.15.0 // indirect
12+
github.com/fsnotify/fsnotify v1.6.0 // indirect
13+
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
14+
github.com/gin-contrib/sse v0.1.0 // indirect
15+
github.com/gin-gonic/gin v1.9.1 // indirect
16+
github.com/go-playground/locales v0.14.1 // indirect
17+
github.com/go-playground/universal-translator v0.18.1 // indirect
18+
github.com/go-playground/validator/v10 v10.15.5 // indirect
19+
github.com/goccy/go-json v0.10.2 // indirect
20+
github.com/gorilla/websocket v1.5.0 // indirect
21+
github.com/hajimehoshi/oto v0.7.1 // indirect
22+
github.com/hashicorp/hcl v1.0.0 // indirect
23+
github.com/json-iterator/go v1.1.12 // indirect
24+
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
25+
github.com/leodido/go-urn v1.2.4 // indirect
26+
github.com/magiconair/properties v1.8.7 // indirect
27+
github.com/mattn/go-colorable v0.1.13 // indirect
28+
github.com/mattn/go-isatty v0.0.20 // indirect
29+
github.com/mitchellh/mapstructure v1.5.0 // indirect
30+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
31+
github.com/modern-go/reflect2 v1.0.2 // indirect
32+
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
33+
github.com/pkg/errors v0.9.1 // indirect
34+
github.com/rjeczalik/notify v0.9.3 // indirect
35+
github.com/sagikazarmark/locafero v0.3.0 // indirect
36+
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
37+
github.com/sourcegraph/conc v0.3.0 // indirect
38+
github.com/spf13/afero v1.10.0 // indirect
39+
github.com/spf13/cast v1.5.1 // indirect
40+
github.com/spf13/pflag v1.0.5 // indirect
41+
github.com/spf13/viper v1.17.0 // indirect
42+
github.com/subosito/gotenv v1.6.0 // indirect
43+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
44+
github.com/ugorji/go/codec v1.2.11 // indirect
45+
go.uber.org/atomic v1.9.0 // indirect
46+
go.uber.org/multierr v1.9.0 // indirect
47+
golang.org/x/arch v0.5.0 // indirect
48+
golang.org/x/crypto v0.14.0 // indirect
49+
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
50+
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
51+
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 // indirect
52+
golang.org/x/net v0.17.0 // indirect
53+
golang.org/x/sys v0.13.0 // indirect
54+
golang.org/x/text v0.13.0 // indirect
55+
google.golang.org/protobuf v1.31.0 // indirect
56+
gopkg.in/ini.v1 v1.67.0 // indirect
57+
gopkg.in/yaml.v3 v3.0.1 // indirect
58+
)

go.sum

Lines changed: 591 additions & 0 deletions
Large diffs are not rendered by default.

http/http.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package http
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/ODDInvictus/aether/logger"
8+
"github.com/ODDInvictus/aether/spotify"
9+
"github.com/gin-gonic/gin"
10+
)
11+
12+
var r *gin.Engine
13+
14+
func Init() *gin.Engine {
15+
r = gin.Default()
16+
17+
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
18+
return fmt.Sprintf("[%s] %s - \"%s %s %s %d %s \"%s\" %s\"\n",
19+
param.TimeStamp.Format(time.RFC3339),
20+
param.ClientIP,
21+
param.Method,
22+
param.Path,
23+
param.Request.Proto,
24+
param.StatusCode,
25+
param.Latency,
26+
param.Request.UserAgent(),
27+
param.ErrorMessage,
28+
)
29+
}))
30+
31+
r.Use(gin.Recovery())
32+
33+
34+
apiRoutes()
35+
36+
return r
37+
}
38+
39+
func apiRoutes() {
40+
r.GET("/hello", func (c *gin.Context) {
41+
c.JSON(200, gin.H{
42+
"message": "World!",
43+
})
44+
})
45+
46+
r.GET("/state", func (c *gin.Context) {
47+
state, err := spotify.Current()
48+
49+
if err != nil {
50+
c.JSON(500, gin.H{
51+
"message": fmt.Sprint(err),
52+
})
53+
}
54+
55+
logger.Verbose(fmt.Sprint(state))
56+
57+
c.JSON(200, gin.H{
58+
"state": state,
59+
})
60+
})
61+
62+
r.POST("/playlist/play", func (c *gin.Context) {
63+
var params PlaylistPlay
64+
65+
if c.ShouldBind(&params) == nil {
66+
ok, err := spotify.Load(params.SpotifyID, true, true)
67+
68+
if !ok {
69+
c.JSON(500, gin.H{
70+
"message": fmt.Sprint(err),
71+
})
72+
}
73+
74+
c.JSON(200, gin.H{
75+
"message": "Success",
76+
})
77+
} else {
78+
c.JSON(400, gin.H{
79+
"message": "Invalid playlist id",
80+
})
81+
}
82+
})
83+
84+
r.POST("/skip", func (c *gin.Context) {
85+
spotify.Next()
86+
})
87+
}

http/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package http
2+
3+
type PlaylistPlay struct {
4+
SpotifyID string `form:"spotify_id"`
5+
}

logger/log.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package logger
2+
3+
import (
4+
"strconv"
5+
"time"
6+
7+
"github.com/fatih/color"
8+
)
9+
10+
var debug bool
11+
12+
func Debug (b bool) {
13+
Log("Setting debug level to " + strconv.FormatBool(b))
14+
debug = b
15+
}
16+
17+
func Warn(stmt string) {
18+
if !debug {
19+
return
20+
}
21+
22+
color.Yellow("[%s] [Wrn] %s\n", time.Now().Format(time.RFC3339), stmt)
23+
}
24+
25+
func Err(stmt string, err error) {
26+
color.Red("[%s] [Err] %s\n", time.Now().Format(time.RFC3339), stmt)
27+
panic(err)
28+
}
29+
30+
func Log(stmt string) {
31+
color.Blue("[%s] [Log] %s\n", time.Now().Format(time.RFC3339), stmt)
32+
}
33+
34+
func Verbose(stmt string) {
35+
if !debug {
36+
return
37+
}
38+
39+
color.Magenta("[%s] [Ver] %s\n", time.Now().Format(time.RFC3339), stmt)
40+
}

main.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"github.com/ODDInvictus/aether/http"
5+
"github.com/ODDInvictus/aether/logger"
6+
"github.com/ODDInvictus/aether/spotify"
7+
"github.com/ODDInvictus/aether/utils"
8+
)
9+
10+
var spotifyState spotify.SpotifyPlayer
11+
12+
func main() {
13+
logger.Log("Starting the Aether")
14+
logger.Debug(true)
15+
utils.LoadConfig()
16+
utils.InitConnectionStatus()
17+
18+
spotify.Init(false)
19+
go spotify.ListenToEvents(&spotifyState)
20+
21+
router := http.Init()
22+
router.Run()
23+
}

spotify/events.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package spotify
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/url"
7+
"os"
8+
"os/signal"
9+
"strconv"
10+
"time"
11+
12+
"github.com/ODDInvictus/aether/logger"
13+
"github.com/gorilla/websocket"
14+
"github.com/spf13/viper"
15+
)
16+
17+
func ListenToEvents(state *SpotifyPlayer) {
18+
Log("Listening to player events")
19+
20+
interrupt := make(chan os.Signal, 1)
21+
signal.Notify(interrupt, os.Interrupt)
22+
23+
u := url.URL{Scheme: "ws", Host: viper.GetString("spotify.ws"), Path: "/events"}
24+
25+
conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
26+
27+
if err != nil {
28+
logger.Err("failed to connect to websocket", err)
29+
}
30+
31+
defer conn.Close()
32+
33+
done := make(chan struct{})
34+
35+
go func() {
36+
defer close(done)
37+
for {
38+
_, message, err := conn.ReadMessage()
39+
if err != nil {
40+
panic("failed to read msg")
41+
}
42+
43+
var res map[string]interface{}
44+
45+
err = json.Unmarshal(message, &res)
46+
47+
if err != nil {
48+
continue
49+
}
50+
51+
Log(fmt.Sprint(res["event"]))
52+
53+
switch res["event"] {
54+
case "contextChanged":
55+
state.contextUri = fmt.Sprint(res["uri"])
56+
case "trackChanged":
57+
state.uri = fmt.Sprint(res["uri"])
58+
case "playbackEnded":
59+
state.paused = true
60+
state.trackTime = 0
61+
case "playbackPaused":
62+
state.paused = true
63+
state.trackTime = int64(res["trackTime"].(float64))
64+
case "playbackResumed":
65+
state.paused = false
66+
state.trackTime = int64(res["trackTime"].(float64))
67+
case "playbackFailed":
68+
state.paused = true
69+
state.trackTime = 0
70+
case "trackSeeked":
71+
state.trackTime = int64(res["trackTime"].(float64))
72+
case "metadataAvailable":
73+
track := Track{}
74+
75+
jsonString, _ := json.Marshal(res["track"])
76+
json.Unmarshal(jsonString, &track)
77+
78+
state.metadata = track
79+
case "playbackHaltStateChanged":
80+
x, err := strconv.ParseBool(fmt.Sprint(res["halted"]))
81+
82+
if err != nil {
83+
state.paused = true
84+
} else {
85+
state.paused = x
86+
}
87+
88+
state.trackTime = int64(res["trackTime"].(float64))
89+
case "panic":
90+
Log("Spotify failed, restarting song")
91+
PlayPause()
92+
PlayPause()
93+
// Load(viper.GetString("fallback.playlist"), true, true)
94+
}
95+
}
96+
}()
97+
98+
ticker := time.NewTicker(time.Second)
99+
defer ticker.Stop()
100+
101+
for {
102+
select {
103+
case <-done:
104+
return
105+
case t := <-ticker.C:
106+
err := conn.WriteMessage(websocket.TextMessage, []byte(t.String()))
107+
if err != nil {
108+
logger.Err("write:", err)
109+
return
110+
}
111+
case <-interrupt:
112+
Log("closing connection...")
113+
114+
// Cleanly close the connection by sending a close message and then
115+
// waiting (with timeout) for the server to close the connection.
116+
err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
117+
if err != nil {
118+
logger.Err("write close:", err)
119+
return
120+
}
121+
select {
122+
case <-done:
123+
case <-time.After(time.Second):
124+
}
125+
return
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)