[WIP] music platform user data scraper
teal-fm atproto

home template

Changed files
+79 -129
cmd
pages
templates
+1 -1
.air.toml
··· 14 14 follow_symlink = false 15 15 full_bin = "" 16 16 include_dir = [] 17 - include_ext = ["go", "tpl", "tmpl", "html"] 17 + include_ext = ["go", "tpl", "tmpl", "html", "gohtml"] 18 18 include_file = [] 19 19 kill_delay = "0s" 20 20 log = "build-errors.log"
+11 -107
cmd/handlers.go
··· 15 15 "github.com/teal-fm/piper/session" 16 16 ) 17 17 18 + type HomeParams struct { 19 + IsLoggedIn bool 20 + LastFMUsername *string 21 + } 22 + 18 23 func home(database *db.DB, pages *pages.Pages) http.HandlerFunc { 19 24 return func(w http.ResponseWriter, r *http.Request) { 20 25 ··· 33 38 log.Printf("Error fetching user %d details for home page: %v", userID, err) 34 39 } 35 40 } 36 - 37 - html := ` 38 - <html> 39 - <head> 40 - <title>Piper - Spotify & Last.fm Tracker</title> 41 - <style> 42 - body { 43 - font-family: Arial, sans-serif; 44 - max-width: 800px; 45 - margin: 0 auto; 46 - padding: 20px; 47 - line-height: 1.6; 48 - } 49 - h1 { 50 - color: #1DB954; /* Spotify green */ 51 - } 52 - .nav { 53 - display: flex; 54 - flex-wrap: wrap; /* Allow wrapping on smaller screens */ 55 - margin-bottom: 20px; 56 - } 57 - .nav a { 58 - margin-right: 15px; 59 - margin-bottom: 5px; /* Add spacing below links */ 60 - text-decoration: none; 61 - color: #1DB954; 62 - font-weight: bold; 63 - } 64 - .card { 65 - border: 1px solid #ddd; 66 - border-radius: 8px; 67 - padding: 20px; 68 - margin-bottom: 20px; 69 - } 70 - .service-status { 71 - font-style: italic; 72 - color: #555; 73 - } 74 - </style> 75 - </head> 76 - <body> 77 - <h1>Piper - Multi-User Spotify & Last.fm Tracker via ATProto</h1> 78 - <div class="nav"> 79 - <a href="/">Home</a>` 80 - 81 - if isLoggedIn { 82 - html += ` 83 - <a href="/current-track">Spotify Current</a> 84 - <a href="/history">Spotify History</a> 85 - <a href="/link-lastfm">Link Last.fm</a>` // Link to Last.fm page 86 - if lastfmUsername != "" { 87 - html += ` <a href="/lastfm/recent">Last.fm Recent</a>` // Show only if linked 88 - } 89 - html += ` 90 - <a href="/api-keys">API Keys</a> 91 - <a href="/login/spotify">Connect Spotify Account</a> 92 - <a href="/logout">Logout</a>` 93 - } else { 94 - html += ` 95 - <a href="/login/atproto">Login with ATProto</a>` 41 + params := HomeParams{ 42 + IsLoggedIn: isLoggedIn, 43 + LastFMUsername: &lastfmUsername, 96 44 } 97 - 98 - html += ` 99 - </div> 100 - 101 - <div class="card"> 102 - <h2>Welcome to Piper</h2> 103 - <p>Piper is a multi-user application that records what you're listening to on Spotify and Last.fm, saving your listening history.</p>` 104 - 105 - if !isLoggedIn { 106 - html += ` 107 - <p>Login with ATProto to get started!</p> 108 - <form action="/login/atproto"> 109 - <label for="handle">handle:</label> 110 - <input type="text" id="handle" name="handle" > 111 - <input type="submit" value="submit"> 112 - </form>` 113 - } else { 114 - html += ` 115 - <p>You're logged in!</p> 116 - <ul> 117 - <li><a href="/login/spotify">Connect your Spotify account</a> to start tracking.</li> 118 - <li><a href="/link-lastfm">Link your Last.fm account</a> to track scrobbles.</li> 119 - </ul> 120 - <p>Once connected, you can check out your:</p> 121 - <ul> 122 - <li><a href="/current-track">Spotify current track</a> or <a href="/history">listening history</a>.</li>` 123 - if lastfmUsername != "" { 124 - html += `<li><a href="/lastfm/recent">Last.fm recent tracks</a>.</li>` 125 - } 126 - html += ` 127 - </ul> 128 - <p>You can also manage your <a href="/api-keys">API keys</a> for programmatic access.</p>` 129 - if lastfmUsername != "" { 130 - html += fmt.Sprintf("<p class='service-status'>Last.fm Username: %s</p>", lastfmUsername) 131 - } else { 132 - html += "<p class='service-status'>Last.fm account not linked.</p>" 133 - } 134 - 45 + err := pages.Execute("home", w, params) 46 + if err != nil { 47 + log.Printf("Error executing template: %v", err) 135 48 } 136 - 137 - html += ` 138 - </div> <!-- Close card div --> 139 - </body> 140 - </html> 141 - ` 142 - pages.Execute("home", w, nil) 143 - 144 - //w.Write([]byte(html)) 145 49 } 146 50 } 147 51
+1 -1
cmd/main.go
··· 107 107 spotifyService: spotifyService, 108 108 atprotoService: atprotoService, 109 109 playingNowService: playingNowService, 110 - pages: pages.NewPages(false), 110 + pages: pages.NewPages(), 111 111 } 112 112 113 113 trackerInterval := time.Duration(viper.GetInt("tracker.interval")) * time.Second
+8 -19
pages/pages.go
··· 1 1 package pages 2 2 3 + // inspired from tangled's implementation 4 + //https://tangled.org/@tangled.org/core/blob/master/appview/pages/pages.go 5 + 3 6 import ( 4 7 "embed" 5 8 "html/template" 6 9 "io" 7 10 "io/fs" 8 - "os" 9 11 "strings" 10 12 "time" 11 13 ) ··· 13 15 //go:embed templates/* 14 16 var Files embed.FS 15 17 16 - // inspired from tangled's implementation 17 - //https://tangled.org/@tangled.org/core/blob/master/appview/pages/pages.go 18 - 19 18 type Pages struct { 20 19 cache *TmplCache[string, *template.Template] 21 - dev bool 22 20 templateDir string // Path to templates on disk for dev mode 23 21 embedFS fs.FS 24 22 } 25 23 26 - func NewPages(dev bool) *Pages { 27 - pages := &Pages{ 28 - cache: NewTmplCache[string, *template.Template](), 29 - dev: dev, 30 - templateDir: "templates", 31 - } 32 - if pages.dev { 33 - pages.embedFS = os.DirFS(pages.templateDir) 34 - } else { 35 - pages.embedFS = Files 24 + func NewPages() *Pages { 25 + return &Pages{ 26 + cache: NewTmplCache[string, *template.Template](), 27 + embedFS: Files, 36 28 } 37 - 38 - return pages 39 29 } 40 30 41 31 func (p *Pages) fragmentPaths() ([]string, error) { ··· 99 89 func (p *Pages) parse(stack ...string) (*template.Template, error) { 100 90 key := strings.Join(stack, "|") 101 91 102 - // never cache in dev mode 103 - if cached, exists := p.cache.Get(key); !p.dev && exists { 92 + if cached, exists := p.cache.Get(key); exists { 104 93 return cached, nil 105 94 } 106 95
+58 -1
pages/templates/home.gohtml
··· 1 1 2 2 {{ define "content" }} 3 3 4 - <h1>Test</h1> 4 + <h1>Piper - Multi-User Spotify & Last.fm Tracker via ATProto</h1> 5 + <div class="nav"> 6 + <a href="/">Home</a> 7 + 8 + {{if .IsLoggedIn}} 9 + <a href="/current-track">Spotify Current</a> 10 + <a href="/history">Spotify History</a> 11 + <a href="/link-lastfm">Link Last.fm</a> 12 + {{ if .LastFMUsername }} 13 + <a href="/lastfm/recent">Last.fm Recent</a> 14 + {{ end }} 15 + <a href="/api-keys">API Keys</a> 16 + <a href="/login/spotify">Connect Spotify Account</a> 17 + <a href="/logout">Logout</a> 18 + {{ else }} 19 + <a href="/login/atproto">Login with ATProto</a> 20 + {{ end }} 21 + </div> 22 + 23 + <div class="card"> 24 + <h2>Welcome to Piper</h2> 25 + <p>Piper is a multi-user application that records what you're listening to on Spotify and Last.fm, saving your listening history.</p> 26 + 27 + {{if .IsLoggedIn}} 28 + <p>You're logged in!</p> 29 + <ul> 30 + <li><a href="/login/spotify">Connect your Spotify account</a> to start tracking.</li> 31 + <li><a href="/link-lastfm">Link your Last.fm account</a> to track scrobbles.</li> 32 + </ul> 33 + <p>Once connected, you can check out your:</p> 34 + <ul> 35 + <li><a href="/current-track">Spotify current track</a> or <a href="/history">listening history</a>.</li> 36 + {{ if .LastFMUsername }} 37 + <li><a href="/lastfm/recent">Last.fm recent tracks</a>.</li> 38 + {{ end }} 39 + 40 + </ul> 41 + <p>You can also manage your <a href="/api-keys">API keys</a> for programmatic access.</p> 42 + 43 + {{ if .LastFMUsername }} 44 + <p class='service-status'>Last.fm Username: {{ .LastFMUsername }}</p> 45 + {{else }} 46 + <p class='service-status'>Last.fm account not linked.</p> 47 + {{end}} 48 + 49 + 50 + {{ else }} 51 + 52 + <p>Login with ATProto to get started!</p> 53 + <form action="/login/atproto"> 54 + <label for="handle">handle:</label> 55 + <input type="text" id="handle" name="handle" > 56 + <input type="submit" value="submit"> 57 + </form> 58 + 59 + 60 + {{ end }} 61 + </div> <!-- Close card div --> 5 62 6 63 {{ end }}