Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
JaiiR320 committed Feb 6, 2024
0 parents commit a8a5534
Show file tree
Hide file tree
Showing 32 changed files with 3,634 additions and 0 deletions.
46 changes: 46 additions & 0 deletions .air.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "bin"

[build]
args_bin = []
bin = "bin\\app.exe"
cmd = "templ generate && go build -o bin/app.exe cmd/main.go"
delay = 1000
exclude_dir = ["assets", "bin", "tmp", "vendor", "testdata"]
exclude_file = ["output.html"]
exclude_regex = [".*_templ.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "templ", "tmpl", "html", "css"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false

[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"

[log]
main_only = false
time = false

[misc]
clean_on_exit = false

[screen]
clear_on_rebuild = false
keep_scroll = true
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
all: templ
@go build -o bin/app.exe cmd/main.go
@./bin/app.exe

templ:
@templ generate

test:
@go test -v ./...

tailwind:
@tailwind -i view/input.css -o static/output.css --watch

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SpotifAI

Binary file added bin/app.exe
Binary file not shown.
1 change: 1 addition & 0 deletions bin/build-errors.log
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
92 changes: 92 additions & 0 deletions bruh.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html id="app" lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Login - Spotify</title>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<base href="/">
<link rel="icon"
href="https://accounts.scdn.co/sso/images/favicon.ace4d8543bbb017893402a1e9d1ac1fa.ico">
<script defer
src="https://accounts.scdn.co/sso/js/indexReact.6feea73dc284cdb1ccc3.js"
sp-bootstrap></script>
<meta id="bootstrap-data"
sp-bootstrap-data='{"phoneFeatureEnabled":false,"previewEnabled":false,"user":false,"tpaState":"AQAZJF9jPrpF8nPmCoStNgh6Hq95rNOWnu7y2wGv7PnXP3KAwbP3MX7DdM/KwCsG9qmp8psdH9z6j3JKZvRsbUB8fGGkiW5fLSHj+uH1Hsmq+qfhSZqmuXOHuge+4JErvMIeIYXr1r7sEtaMJ2OPwJ6PPa0yHD94EvK3EhB7vpyjlg33yW77rSrounEauHtv8sj4b9uYLsoJV2ItrcfifdDZWZrTWyAa1FcrbEAbZirq9HbqQGLrdpiAyGlpcMIL2e+Ht00jRpaqUkHb7ew9n2llb8cFyLgyXsVbrbhy6pbKhYaBhAVKMOJAHe9IIN1kj4YGYTbV1uAmPy2iSR+belgPPxLxg6G4RR/8YB3L7Yt8iAppLvCP4X2En2+6+sgTlwLDf+rnWHC4ChJ7rUEOCzvQoLMdcfdQG3OZazPqPH9AqWZbdY/88pqKEQbWBl438Qwn8oKFml2YWYkFW5eS1fel2pgpv5JN/Skv2kUmuTfbj2p254K8MZp2yEX48uiNApq7PJWqclr/Ld5xGuYCSMcS","geoLocationCountryCode":"US","state":"","flowCtx":"f17e5cf8-c3c8-4a0e-a99d-523e281cb2bf:1707218338","BON":["0","0",267224599]}'
sp-component="login"
sp-translations-data='eyJlcnJvclRpdGxlIjoiRXJyb3IiLCJsb2dpblRpdGxlIjoiTG9naW4iLCJmb3Jnb3RZb3VyUGFzc3dvcmRVc2VybmFtZSI6IkZvcmdvdCB5b3VyIHBhc3N3b3JkPyIsImRvbnRIYXZlQW5BY2NvdW50IjoiRG9uJ3QgaGF2ZSBhbiBhY2NvdW50PyIsImlucHV0VXNlcm5hbWUiOiJFbWFpbCBhZGRyZXNzIG9yIHVzZXJuYW1lIiwiaW5wdXRFbWFpbE9yVXNlcm5hbWUiOiJFbWFpbCBvciB1c2VybmFtZSIsImlucHV0UGFzc3dvcmQiOiJQYXNzd29yZCIsImNoZWNrYm94UmVtZW1iZXJNZSI6IlJlbWVtYmVyIG1lIiwiZXJyb3JGb3JtRGVmYXVsdCI6Ik9vcHMhIFNvbWV0aGluZyB3ZW50IHdyb25nLCBwbGVhc2UgdHJ5IGFnYWluIG9yIGNoZWNrIG91dCBvdXIgPGhlbHBMaW5rPmhlbHAgYXJlYTwvaGVscExpbms+IiwiZXJyb3JJbnZhbGlkQ3JlZGVudGlhbHMiOiJJbmNvcnJlY3QgdXNlcm5hbWUgb3IgcGFzc3dvcmQuIiwiZXJyb3JJbnZhbGlkQ3JlZGVudGlhbHNJbXByb3ZlZCI6IkluY29ycmVjdCBlbWFpbCBhZGRyZXNzLCB1c2VybmFtZSBvciBwYXNzd29yZC4iLCJlcnJvclVua25vd24iOiJPb3BzISBTb21ldGhpbmcgd2VudCB3cm9uZywgcGxlYXNlIHRyeSBhZ2FpbiBvciBjaGVjayBvdXQgb3VyIDxoZWxwTGluaz5oZWxwIGFyZWE8L2hlbHBMaW5rPiIsImVycm9yVHJhbnNpZW50IjoiQW4gZXJyb3IgaGFzIG9jY3VycmVkIHByb2Nlc3NpbmcgeW91ciBsb2dpbi4gUGxlYXNlIHRyeSBhZ2Fpbi4iLCJlcnJvckZhY2Vib29rQWNjb3VudCI6IllvdSBkbyBub3QgaGF2ZSBhIFNwb3RpZnkgYWNjb3VudCBjb25uZWN0ZWQgdG8geW91ciBGYWNlYm9vayBhY2NvdW50LiBJZiB5b3UgaGF2ZSBhIFNwb3RpZnkgYWNjb3VudCwgcGxlYXNlIGxvZyBpbiB3aXRoIHlvdXIgU3BvdGlmeSBjcmVkZW50aWFscy4gSWYgeW91IGRvIG5vdCBoYXZlIGEgU3BvdGlmeSBhY2NvdW50LCA8bGlua1dpdGhIcmVmPnNpZ24gdXA8L2xpbmtXaXRoSHJlZj4uIiwiZXJyb3JTZXJ2ZXJFcnJvciI6IkFuIGVycm9yIGhhcyBvY2N1cnJlZCBwcm9jZXNzaW5nIHlvdXIgcmVxdWVzdC4gUGxlYXNlIHRyeSBhZ2Fpbi4iLCJlcnJvclVzZXJuYW1lUmVxdWlyZWQiOiJQbGVhc2UgZW50ZXIgeW91ciBTcG90aWZ5IHVzZXJuYW1lIG9yIGVtYWlsIGFkZHJlc3MuIiwiZXJyb3JVc2VybmFtZUludmFsaWRDaGFyYWN0ZXJzIjoiRm9yYmlkZGVuIGNoYXJhY3RlcihzKSB7Zm9yYmlkZGVuQ2hhcnN9IGluIHVzZXJuYW1lLiIsImVycm9yUGFzc3dvcmRSZXF1aXJlZCI6IlBsZWFzZSBlbnRlciB5b3VyIHBhc3N3b3JkLiIsImVycm9yTm9JbnRlcm5ldENvbm5lY3Rpdml0eSI6IlByb2JsZW0gY29ubmVjdGluZy4gQ2hlY2sgeW91ciBpbnRlcm5ldCBjb25uZWN0aW9uIGFuZCB7dHJ5QWdhaW5MaW5rfS4iLCJlcnJvclRyeUFnYWluIjoidHJ5IGFnYWluIiwibG9nSW4iOiJMb2cgSW4iLCJsb2dJblRvU3BvdGlmeSI6IkxvZyBpbiB0byBTcG90aWZ5Iiwic2lnblVwRm9yU3BvdGlmeSI6IlNpZ24gdXAgZm9yIFNwb3RpZnkiLCJvciI6Im9yIiwibG9naW5Ub0NvbnRpbnVlIjoiVG8gY29udGludWUsIGxvZyBpbiB0byBTcG90aWZ5LiIsImVycm9yVmFsaWRhdGlvbkludmFsaWRDb2RlIjoiVGhpcyBjb2RlIGlzIGludmFsaWQuIENoZWNrIHRoZSBTTVMgYW5kIHRyeSBhZ2Fpbi4iLCJlcnJvclN1Ym1pdFRvb2tUb29Mb25nVG9DcmVhdGUiOiJJdCB0b29rIHRvbyBsb25nIHRvIGNvbXBsZXRlIHlvdXIgcmVxdWVzdC4gVHJ5IGFnYWluLiIsImVycm9yQXBwbGVBY2NvdW50IjoiWW91IGRvIG5vdCBoYXZlIGEgU3BvdGlmeSBhY2NvdW50IGNvbm5lY3RlZCB0byB5b3VyIEFwcGxlIElELiBJZiB5b3UgaGF2ZSBhIFNwb3RpZnkgYWNjb3VudCwgcGxlYXNlIHRyeSBsb2cgaW4gd2l0aCB5b3VyIFNwb3RpZnkgZW1haWwgb3IgdXNlcm5hbWUuIElmIHlvdSBkbyBub3QgaGF2ZSBhIFNwb3RpZnkgYWNjb3VudCwgcGxlYXNlIHNpZ24gdXAuIiwiY29udGludWVXaXRoQXBwbGUiOiJDb250aW51ZSB3aXRoIEFwcGxlIiwiY29udGludWVXaXRoRmFjZWJvb2siOiJDb250aW51ZSB3aXRoIEZhY2Vib29rIiwiY29udGludWVXaXRoUGhvbmVOdW1iZXIiOiJDb250aW51ZSB3aXRoIHBob25lIG51bWJlciIsImNvbnRpbnVlV2l0aEdvb2dsZSI6IkNvbnRpbnVlIHdpdGggR29vZ2xlIiwiZXJyb3JHb29nbGVBY2NvdW50IjoiWW91IGRvIG5vdCBoYXZlIGEgU3BvdGlmeSBhY2NvdW50IGNvbm5lY3RlZCB0byB5b3VyIEdvb2dsZSBBY2NvdW50LiBJZiB5b3UgaGF2ZSBhIFNwb3RpZnkgYWNjb3VudCwgcGxlYXNlIHRyeSBsb2cgaW4gd2l0aCB5b3VyIFNwb3RpZnkgZW1haWwgb3IgdXNlcm5hbWUuIElmIHlvdSBkbyBub3QgaGF2ZSBhIFNwb3RpZnkgYWNjb3VudCwgcGxlYXNlIHNpZ24gdXAuIiwicmVjYXB0Y2hhTGVnYWxOb3RpY2UiOiJUaGlzIHNpdGUgaXMgcHJvdGVjdGVkIGJ5IHJlQ0FQVENIQSBhbmQgdGhlIEdvb2dsZSA8Z29vZ2xlUHJpdmFjeVBvbGljeUxpbms+UHJpdmFjeSBQb2xpY3k8L2dvb2dsZVByaXZhY3lQb2xpY3lMaW5rPiBhbmQgPGdvb2dsZVRlcm1zTGluaz5UZXJtcyBvZiBTZXJ2aWNlPC9nb29nbGVUZXJtc0xpbms+IGFwcGx5LiIsImxvZ2luV2l0aG91dFBhc3N3b3JkIjoiTG9nIGluIHdpdGhvdXQgcGFzc3dvcmQiLCJtYWdpY19saW5rX3BvcHVwX2hlYWRlciI6IkhhdmluZyB0cm91YmxlIGxvZ2dpbmcgaW4/IiwibWFnaWNfbGlua19wb3B1cF9ib2R5IjoiV2Ugc2VudCBhbiBlbWFpbCB3aXRoIGEgbGluayB0aGF0IHdpbGwgbG9nIHlvdSBpbiB3aXRob3V0IGEgcGFzc3dvcmQuIiwiZGlhbG9nX19hY3Rpb25fcmV0cnlfbG9naW4iOiJUcnkgYW5vdGhlciBwYXNzd29yZCJ9'>
<style>
body {
background-color: #121212;
margin: 0;
}
#root {
min-height: 100vh;
}
.loading-indicator-container {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.loading-indicator-container * {
box-sizing: border-box;
}
.loading-indicator {
content: "";
width: 56px;
inline-size: 56px;
block-size: 12.4px;
height: 12.4px;
overflow-clip-margin: content-box;
overflow: hidden;
}
.loading-indicator circle {
fill: white;
animation: loading-indicator-kf 1.32s linear infinite;
transform-origin: center center;
opacity: 0.5;
}
.loading-indicator circle:nth-of-type(2) {
animation-delay: 0.1s;
}
.loading-indicator circle:nth-of-type(3) {
animation-delay: 0.2s;
}
@keyframes loading-indicator-kf {
0% {
animation-timing-function: cubic-bezier(1, 0, 0.7, 1);
opacity: 0.5;
transform: scale(1);
}
40% {
animation-timing-function: cubic-bezier(0.3, 0, 0, 1);
opacity: 0.75;
transform: scale(1.3);
}
72.5% {
animation-timing-function: linear;
opacity: 0.5;
transform: scale(1);
}
100% {
opacity: 0.5;
transform: scale(1);
}
}
</style>
</head>
<body class="encore-dark-theme">
<div id="root">
<div class="loading-indicator-container">
<svg class="loading-indicator" xmlns="http://www.w3.org/2000/svg"
x="0px" y="0px" viewBox="0 0 1 100" xml:space="preserve"
role="progressbar" aria-valuetext="Loading">
<circle stroke="none" cx="-140" cy="50" r="32"></circle>
<circle stroke="none" cx="0" cy="50" r="32"></circle>
<circle stroke="none" cx="140" cy="50" r="32"></circle>
</svg>
</div>
</div>
</body>
</html>
152 changes: 152 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package main

import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"

"github.com/JaiiR320/SpotifAI/model"
"github.com/JaiiR320/SpotifAI/view"
"github.com/a-h/templ"
"github.com/labstack/echo/v4"
)

var client_id = `04c4fff349274db8b607e7a927f2ca5a`
var client_secret = `a97b3bed058d4a1ea4502654bcf554f6`

func main() {
router := echo.New()

router.Static("/static", "/static")

router.GET("/", HandleShowHome)
router.GET("/login", HandleSpotifyAuth)

router.GET("/callback", HandleSpotifyCallback)

// scripts.ParsePlaylist("example.json")

log.Fatal(router.Start(":3000"))
}

func HandleShowHome(c echo.Context) error {
return Render(c, view.Home())
}

func HandleSpotifyAuth(c echo.Context) error {
urlStr := `https://accounts.spotify.com/authorize`

data := url.Values{}
data.Set("response_type", "code")
data.Set("client_id", client_id)
data.Set("scope", "user-read-private user-library-read")
data.Set("redirect_uri", "http://localhost:3000/callback")

urlStr = fmt.Sprintf("%s?%s", urlStr, data.Encode())

return c.Redirect(http.StatusTemporaryRedirect, urlStr)
}

func HandleSpotifyCallback(c echo.Context) error {
err := getToken(c)
if err != nil {
return err
}

err = getUser()
if err != nil {
log.Println("GET USER ", err)
return err
}

c.QueryParams().Del("code")

return c.Redirect(http.StatusFound, "/")
}

type TokenResponse struct {
AccessToken string `json:"access_token"`
}

func getToken(c echo.Context) error {
code := c.QueryParam("code")

data := url.Values{}
data.Set("grant_type", "authorization_code")
data.Set("code", code)
data.Set("redirect_uri", "http://localhost:3000/callback")

req, err := http.NewRequest("POST", "https://accounts.spotify.com/api/token", strings.NewReader(data.Encode()))
if err != nil {
return err
}

req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Authorization", "Basic "+client_id)
req.Header.Set("Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(client_id+":"+client_secret)))

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

var tokenResponse TokenResponse
err = json.Unmarshal(body, &tokenResponse)
if err != nil {
return err
}

model.AccessToken = tokenResponse.AccessToken
log.Println(model.AccessToken)
return nil
}

func getUser() error {
req, err := http.NewRequest("GET", "https://api.spotify.com/v1/me", nil)
if err != nil {
return err
}

req.Header.Set("Authorization", "Bearer "+model.AccessToken)

client := &http.Client{}

resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

var user model.User

err = json.Unmarshal(body, &user)

if err != nil {
return err
}

model.CurrentUser = user
return nil
}

func Render(c echo.Context, component templ.Component) error {
return component.Render(c.Request().Context(), c.Response())
}
13 changes: 13 additions & 0 deletions cmd/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
"encoding/json"

"github.com/labstack/echo/v4"
)

func WriteJSON(c echo.Context, status int, v any) error {
c.Response().Header().Set("Content-Type", "application/json")
c.Response().WriteHeader(status)
return json.NewEncoder(c.Response()).Encode(v)
}
Loading

0 comments on commit a8a5534

Please sign in to comment.