- Images now pull from spotify if setup!
- Show top artists/album
This commit is contained in:
Daniel Mason 2021-04-08 20:46:31 +12:00
parent b1de018b27
commit 8294791abe
Signed by: idanoo
GPG Key ID: 387387CDBC02F132
12 changed files with 278 additions and 106 deletions

View File

@ -3,7 +3,7 @@ stages:
- bundle - bundle
variables: variables:
VERSION: 0.0.24 VERSION: 0.0.25
build-go: build-go:
image: golang:1.16.2 image: golang:1.16.2

View File

@ -1,3 +1,7 @@
# 0.0.25
- Images now pull from spotify if setup!
- Show top artists/album
# 0.0.24 # 0.0.24
- Spotify will now add images on scrobble - Spotify will now add images on scrobble
- Renamed /api/v1/track to tracks to bypass blocklists... (uBlock :() - Renamed /api/v1/track to tracks to bypass blocklists... (uBlock :()

View File

@ -15,6 +15,17 @@ type Artist struct {
SpotifyID string `json:"spotify_id"` SpotifyID string `json:"spotify_id"`
} }
type TopArtist struct {
UUID string `json:"uuid"`
Name string `json:"name"`
Img string `json:"img"`
Plays int `json:"plays"`
}
type TopArtists struct {
Artists map[int]TopArtist `json:"artists"`
}
// insertArtist - This will return if it exists or create it based on MBID > Name // insertArtist - This will return if it exists or create it based on MBID > Name
func insertArtist(name string, mbid string, spotifyId string, img string, tx *sql.Tx) (Artist, error) { func insertArtist(name string, mbid string, spotifyId string, img string, tx *sql.Tx) (Artist, error) {
artist := Artist{} artist := Artist{}
@ -114,3 +125,42 @@ func getArtistByUUID(uuid string) (Artist, error) {
return artist, nil return artist, nil
} }
func getTopArtists(userUuid string) (TopArtists, error) {
var topArtist TopArtists
rows, err := db.Query("SELECT BIN_TO_UUID(`artists`.`uuid`, true), `artists`.`name`, IFNULL(`artists`.`img`,''), count(*) "+
"FROM `scrobbles` "+
"JOIN `tracks` ON `tracks`.`uuid` = `scrobbles`.`track` "+
"JOIN track_artist ON track_artist.track = tracks.uuid "+
"JOIN artists ON track_artist.artist = artists.uuid "+
"WHERE `scrobbles`.`user` = UUID_TO_BIN(?, true) "+
"GROUP BY `artists`.`uuid` "+
"ORDER BY count(*) DESC "+
"LIMIT 14;",
userUuid)
if err != nil {
log.Printf("Failed to fetch top artist: %+v", err)
return topArtist, errors.New("Failed to fetch top artist")
}
defer rows.Close()
i := 1
tempArtists := make(map[int]TopArtist)
for rows.Next() {
var artist TopArtist
err := rows.Scan(&artist.UUID, &artist.Name, &artist.Img, &artist.Plays)
if err != nil {
log.Printf("Failed to fetch artist: %+v", err)
return topArtist, errors.New("Failed to fetch artist")
}
tempArtists[i] = artist
i++
}
topArtist.Artists = tempArtists
return topArtist, nil
}

View File

@ -214,6 +214,102 @@ func ParseSpotifyInput(userUUID string, data spotify.RecentlyPlayedItem, client
return nil return nil
} }
func (track *Track) updateImageFromSpotify() error { // updateImageDataFromSpotify update artist/album images from spotify ;D
func (user *User) updateImageDataFromSpotify() error {
// Check that data is set before we attempt to pull
val, _ := getConfigValue("SPOTIFY_APP_SECRET")
if val == "" {
return nil
}
// TO BE REWORKED TO NOT USE A DAMN USER ARGHHH
dbToken, err := user.getSpotifyTokens()
if err != nil {
return nil
}
token := new(oauth2.Token)
token.AccessToken = dbToken.AccessToken
token.RefreshToken = dbToken.RefreshToken
token.Expiry = dbToken.Expiry
token.TokenType = "Bearer"
auth := getSpotifyAuthHandler()
client := auth.NewClient(token)
client.AutoRetry = true
rows, err := db.Query("SELECT BIN_TO_UUID(`uuid`, true), `name` FROM `artists` WHERE IFNULL(`img`,'') = '' LIMIT 50")
if err != nil {
log.Printf("Failed to fetch config: %+v", err)
return errors.New("Failed to fetch artists")
}
toUpdate := make(map[string]string)
for rows.Next() {
var uuid string
var name string
err := rows.Scan(&uuid, &name)
if err != nil {
log.Printf("Failed to fetch artists: %+v", err)
rows.Close()
return errors.New("Failed to fetch artist")
}
res, err := client.Search(name, spotify.SearchTypeArtist)
if len(res.Artists.Artists) > 0 {
if len(res.Artists.Artists[0].Images) > 0 {
toUpdate[uuid] = res.Artists.Artists[0].Images[0].URL
}
}
}
rows.Close()
var artist Artist
tx, _ := db.Begin()
for uuid, img := range toUpdate {
artist, err = getArtistByUUID(uuid)
if err != nil {
continue
}
_ = artist.updateArtist("img", img, tx)
}
tx.Commit()
rows, err = db.Query("SELECT BIN_TO_UUID(`uuid`, true), `name` FROM `albums` WHERE IFNULL(`img`,'') = '' LIMIT 50")
if err != nil {
log.Printf("Failed to fetch config: %+v", err)
return errors.New("Failed to fetch artists")
}
toUpdate = make(map[string]string)
for rows.Next() {
var uuid string
var name string
err := rows.Scan(&uuid, &name)
if err != nil {
log.Printf("Failed to fetch albums: %+v", err)
rows.Close()
return errors.New("Failed to fetch album")
}
res, err := client.Search(name, spotify.SearchTypeAlbum)
if len(res.Albums.Albums) > 0 {
if len(res.Albums.Albums[0].Images) > 0 {
toUpdate[uuid] = res.Albums.Albums[0].Images[0].URL
}
}
}
rows.Close()
var album Album
tx, _ = db.Begin()
for uuid, img := range toUpdate {
album, err = getAlbumByUUID(uuid)
if err != nil {
continue
}
_ = album.updateAlbum("img", img, tx)
}
tx.Commit()
return nil return nil
} }

View File

@ -550,13 +550,13 @@ func getArtists(w http.ResponseWriter, r *http.Request) {
return return
} }
artist, err := getArtistByUUID(uuid) track, err := getTopArtists(uuid)
if err != nil { if err != nil {
throwOkError(w, err.Error()) throwOkError(w, err.Error())
return return
} }
json, _ := json.Marshal(&artist) json, _ := json.Marshal(&track)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write(json) w.Write(json)
} }
@ -665,7 +665,7 @@ func getServerInfo(w http.ResponseWriter, r *http.Request) {
} }
info := ServerInfo{ info := ServerInfo{
Version: "0.0.24", Version: "0.0.25",
RegistrationEnabled: cachedRegistrationEnabled, RegistrationEnabled: cachedRegistrationEnabled,
} }

View File

@ -8,11 +8,10 @@ import (
var endTicker chan bool var endTicker chan bool
func StartBackgroundWorkers() { func StartBackgroundWorkers() {
updateSpotifyData()
endTicker := make(chan bool) endTicker := make(chan bool)
hourTicker := time.NewTicker(time.Hour) hourTicker := time.NewTicker(time.Duration(1) * time.Hour)
minuteTicker := time.NewTicker(time.Duration(60) * time.Second) minuteTicker := time.NewTicker(time.Duration(60) * time.Second)
go func() { go func() {
@ -25,6 +24,9 @@ func StartBackgroundWorkers() {
// Clear old password reset tokens // Clear old password reset tokens
clearOldResetTokens() clearOldResetTokens()
// Attempt to pull missing images from spotify - hackerino version!
user, _ := getUserByUsername("idanoo")
user.updateImageDataFromSpotify()
case <-minuteTicker.C: case <-minuteTicker.C:
// Update playdata from spotify // Update playdata from spotify
updateSpotifyData() updateSpotifyData()

View File

@ -178,11 +178,11 @@ func getTrackByUUID(uuid string) (Track, error) {
func getTopTracks(userUuid string) (TopTracks, error) { func getTopTracks(userUuid string) (TopTracks, error) {
var topTracks TopTracks var topTracks TopTracks
rows, err := db.Query("SELECT BIN_TO_UUID(`tracks`.`uuid`, true), `tracks`.`name`, IFNULL(`artists`.`img`,''), count(*) "+ rows, err := db.Query("SELECT BIN_TO_UUID(`tracks`.`uuid`, true), `tracks`.`name`, IFNULL(`albums`.`img`,''), count(*) "+
"FROM `scrobbles` "+ "FROM `scrobbles` "+
"JOIN `tracks` ON `tracks`.`uuid` = `scrobbles`.`track` "+ "JOIN `tracks` ON `tracks`.`uuid` = `scrobbles`.`track` "+
"JOIN track_artist ON track_artist.track = tracks.uuid "+ "JOIN track_album ON track_album.track = tracks.uuid "+
"JOIN artists ON track_artist.artist = artists.uuid "+ "JOIN albums ON track_album.album = albums.uuid "+
"WHERE `user` = UUID_TO_BIN(?, true) "+ "WHERE `user` = UUID_TO_BIN(?, true) "+
"GROUP BY `scrobbles`.`track` "+ "GROUP BY `scrobbles`.`track` "+
"ORDER BY count(*) DESC "+ "ORDER BY count(*) DESC "+

View File

@ -326,3 +326,12 @@ export const getTopTracks = (uuid) => {
return handleErrorResp(error) return handleErrorResp(error)
}); });
} }
export const getTopArtists = (uuid) => {
return axios.get(process.env.REACT_APP_API_URL + "artists/top/" + uuid).then(
(data) => {
return data.data;
}).catch((error) => {
return handleErrorResp(error)
});
}

View File

@ -3,13 +3,13 @@ import './TopTable.css'
import TopTableBox from './TopTableBox'; import TopTableBox from './TopTableBox';
const TopTable = (props) => { const TopTable = (props) => {
if (!props.items || !props.items.tracks) { if (!props.items || Object.keys(props.items).length < 1) {
return ( return (
<span>No data.</span> <span>Not enough data to show top {props.type}s.<br/></span>
) )
} }
let tracks = props.items.tracks; let tracks = props.items;
return ( return (
<div> <div>
@ -24,101 +24,105 @@ const TopTable = (props) => {
img={tracks[1].img} img={tracks[1].img}
/> />
</div> </div>
<div className="biggestBox"> { Object.keys(props.items).length > 5 &&
<div className="biggestBox">
<TopTableBox
size={150}
number="2"
title={tracks[2].name}
link={"/" + props.type + "/" + tracks[2].uuid}
img={tracks[2].img}
/>
<TopTableBox
size={150}
number="3"
title={tracks[3].name}
link={"/" + props.type + "/" + tracks[3].uuid}
img={tracks[3].img}
/>
<TopTableBox
size={150}
number="4"
title={tracks[4].name}
link={"/" + props.type + "/" + tracks[4].uuid}
img={tracks[4].img}
/>
<TopTableBox
size={150}
number="5"
title={tracks[5].name}
link={"/" + props.type + "/" + tracks[5].uuid}
img={tracks[5].img}
/>
</div>
}
{ Object.keys(props.items).length >= 14 &&
<div className="biggestBox">
<TopTableBox <TopTableBox
size={150} size={100}
number="2" number="6"
title={tracks[2].name} title={tracks[6].name}
link={"/" + props.type + "/" + tracks[2].uuid} link={"/" + props.type + "/" + tracks[6].uuid}
img={tracks[2].img} img={tracks[6].img}
/> />
<TopTableBox <TopTableBox
size={150} size={100}
number="3" number="7"
title={tracks[3].name} title={tracks[7].name}
link={"/" + props.type + "/" + tracks[3].uuid} link={"/" + props.type + "/" + tracks[7].uuid}
img={tracks[3].img} img={tracks[7].img}
/> />
<TopTableBox <TopTableBox
size={150} size={100}
number="4" number="8"
title={tracks[4].name} title={tracks[8].name}
link={"/" + props.type + "/" + tracks[4].uuid} link={"/" + props.type + "/" + tracks[8].uuid}
img={tracks[4].img} img={tracks[8].img}
/> />
<TopTableBox <TopTableBox
size={150} size={100}
number="5" number="9"
title={tracks[5].name} title={tracks[9].name}
link={"/" + props.type + "/" + tracks[5].uuid} link={"/" + props.type + "/" + tracks[9].uuid}
img={tracks[5].img} img={tracks[9].img}
/> />
</div> <TopTableBox
<div className="biggestBox"> size={100}
<TopTableBox number="10"
size={100} title={tracks[10].name}
number="6" link={"/" + props.type + "/" + tracks[10].uuid}
title={tracks[6].name} img={tracks[10].img}
link={"/" + props.type + "/" + tracks[6].uuid} />
img={tracks[6].img} <TopTableBox
/> size={100}
<TopTableBox number="11"
size={100} title={tracks[11].name}
number="7" link={"/" + props.type + "/" + tracks[11].uuid}
title={tracks[7].name} img={tracks[11].img}
link={"/" + props.type + "/" + tracks[7].uuid} />
img={tracks[7].img} <TopTableBox
/> size={100}
<TopTableBox number="12"
size={100} title={tracks[12].name}
number="8" link={"/" + props.type + "/" + tracks[12].uuid}
title={tracks[8].name} img={tracks[12].img}
link={"/" + props.type + "/" + tracks[8].uuid} />
img={tracks[8].img} <TopTableBox
/> size={100}
<TopTableBox number="13"
size={100} title={tracks[13].name}
number="9" link={"/" + props.type + "/" + tracks[13].uuid}
title={tracks[9].name} img={tracks[13].img}
link={"/" + props.type + "/" + tracks[9].uuid} />
img={tracks[9].img} <TopTableBox
/> size={100}
<TopTableBox number="14"
size={100} title={tracks[14].name}
number="10" link={"/" + props.type + "/" + tracks[14].uuid}
title={tracks[10].name} img={tracks[14].img}
link={"/" + props.type + "/" + tracks[10].uuid} />
img={tracks[10].img} </div>
/> }
<TopTableBox
size={100}
number="11"
title={tracks[11].name}
link={"/" + props.type + "/" + tracks[11].uuid}
img={tracks[11].img}
/>
<TopTableBox
size={100}
number="12"
title={tracks[12].name}
link={"/" + props.type + "/" + tracks[12].uuid}
img={tracks[12].img}
/>
<TopTableBox
size={100}
number="13"
title={tracks[13].name}
link={"/" + props.type + "/" + tracks[13].uuid}
img={tracks[13].img}
/>
<TopTableBox
size={100}
number="14"
title={tracks[14].name}
link={"/" + props.type + "/" + tracks[14].uuid}
img={tracks[14].img}
/>
</div>
</div> </div>
</div> </div>
); );

View File

@ -10,7 +10,6 @@ img {
.topOverlay { .topOverlay {
position: absolute; position: absolute;
margin: 5px;
padding: 2px 5px 5px 5px; padding: 2px 5px 5px 5px;
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
line-height: 0.6em; line-height: 0.6em;

View File

@ -20,7 +20,7 @@ const TopTableBox = (props) => {
height: `${props.size}px`, height: `${props.size}px`,
float: `left`, float: `left`,
}} > }} >
<div className="topOverlay" style={{ maxWidth: `${props.size-'10'}px` }}> <div className="topOverlay" style={{ maxWidth: `${props.size-'5'}px` }}>
<span className="topText" style={{ <span className="topText" style={{
fontSize: `${props.size === 300 ? '11pt' : (props.size === 150 ? '8pt': '8pt' )}` fontSize: `${props.size === 300 ? '11pt' : (props.size === 150 ? '8pt': '8pt' )}`
}}>#{props.number} {props.title}</span> }}>#{props.number} {props.title}</span>

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import '../App.css'; import '../App.css';
import './Profile.css'; import './Profile.css';
import ScaleLoader from 'react-spinners/ScaleLoader'; import ScaleLoader from 'react-spinners/ScaleLoader';
import { getProfile, getTopTracks } from '../Api/index' import { getProfile, getTopTracks, getTopArtists } from '../Api/index'
import ScrobbleTable from '../Components/ScrobbleTable' import ScrobbleTable from '../Components/ScrobbleTable'
import TopTable from '../Components/TopTable' import TopTable from '../Components/TopTable'
@ -10,6 +10,7 @@ const Profile = (route) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [profile, setProfile] = useState({}); const [profile, setProfile] = useState({});
const [topTracks, setTopTracks] = useState({}) const [topTracks, setTopTracks] = useState({})
const [topArtists, setTopArtists] = useState({})
let username = false; let username = false;
if (route && route.match && route.match.params && route.match.params.uuid) { if (route && route.match && route.match.params && route.match.params.uuid) {
@ -30,9 +31,14 @@ const Profile = (route) => {
// Fetch top tracks // Fetch top tracks
getTopTracks(data.uuid) getTopTracks(data.uuid)
.then(data => { .then(data => {
setTopTracks(data) setTopTracks(data.tracks)
} })
)
// Fetch top artists
getTopArtists(data.uuid)
.then(data => {
setTopArtists(data.artists)
})
setLoading(false); setLoading(false);
}) })
@ -63,6 +69,8 @@ const Profile = (route) => {
<div className="pageBody"> <div className="pageBody">
<TopTable type="track" items={topTracks} /> <TopTable type="track" items={topTracks} />
<br/> <br/>
<TopTable type="artist" items={topArtists} />
<br/>
Last 10 scrobbles...<br/> Last 10 scrobbles...<br/>
<ScrobbleTable data={profile.scrobbles}/> <ScrobbleTable data={profile.scrobbles}/>
</div> </div>