mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-22 08:25:14 +00:00
0.0.25
- Images now pull from spotify if setup! - Show top artists/album
This commit is contained in:
parent
b1de018b27
commit
8294791abe
@ -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
|
||||||
|
@ -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 :()
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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 "+
|
||||||
|
@ -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)
|
||||||
|
});
|
||||||
|
}
|
@ -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,6 +24,7 @@ const TopTable = (props) => {
|
|||||||
img={tracks[1].img}
|
img={tracks[1].img}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{ Object.keys(props.items).length > 5 &&
|
||||||
<div className="biggestBox">
|
<div className="biggestBox">
|
||||||
<TopTableBox
|
<TopTableBox
|
||||||
size={150}
|
size={150}
|
||||||
@ -54,6 +55,8 @@ const TopTable = (props) => {
|
|||||||
img={tracks[5].img}
|
img={tracks[5].img}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
{ Object.keys(props.items).length >= 14 &&
|
||||||
<div className="biggestBox">
|
<div className="biggestBox">
|
||||||
<TopTableBox
|
<TopTableBox
|
||||||
size={100}
|
size={100}
|
||||||
@ -119,6 +122,7 @@ const TopTable = (props) => {
|
|||||||
img={tracks[14].img}
|
img={tracks[14].img}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user