Add GPM links.

pull/7/head
Sander Vocke 5 years ago
parent 03db6af1f8
commit 535ff867a9
  1. 26
      client/src/App.tsx
  2. 8
      client/src/api.ts
  3. 109
      client/src/assets/googleplaymusic_icon.svg
  4. 7
      client/src/components/ItemListLoadedArtistItem.tsx
  5. 7
      client/src/components/ItemListLoadedSongItem.tsx
  6. 8
      client/src/types/DisplayItem.tsx
  7. 20
      scripts/gpm_retrieve/gpm_retrieve.py
  8. 5
      server/endpoints/ArtistDetailsEndpointHandler.ts
  9. 1
      server/endpoints/CreateSongEndpointHandler.ts
  10. 1
      server/endpoints/ModifyArtistEndpointHandler.ts
  11. 1
      server/endpoints/ModifySongEndpointHandler.ts
  12. 1
      server/endpoints/SongDetailsEndpointHandler.ts
  13. 1
      server/models/album.js
  14. 1
      server/models/artist.js
  15. 1
      server/models/song.js

@ -1,12 +1,15 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Paper } from '@material-ui/core'; import { Paper } from '@material-ui/core';
import StoreIcon from '@material-ui/icons/Store';
import * as serverApi from './api'; import * as serverApi from './api';
import AppBar, { ActiveTab as AppBarActiveTab } from './components/AppBar'; import AppBar, { ActiveTab as AppBarActiveTab } from './components/AppBar';
import ItemList from './components/ItemList'; import ItemList from './components/ItemList';
import ItemListItem from './components/ItemListItem'; import ItemListItem from './components/ItemListItem';
import { SongDisplayItem, ArtistDisplayItem } from './types/DisplayItem'; import { SongDisplayItem, ArtistDisplayItem } from './types/DisplayItem';
import { ReactComponent as GooglePlayIcon } from './assets/googleplaymusic_icon.svg';
import { import {
BrowserRouter as Router, BrowserRouter as Router,
Switch, Switch,
@ -23,6 +26,12 @@ interface ArtistItemProps {
id: Number, id: Number,
} }
const getStoreIcon = (url: String) => {
if (url.includes('play.google.com')) {
return <GooglePlayIcon height='30px' width='30px'/>;
}
return <StoreIcon/>;
}
function SongItem(props: SongItemProps) { function SongItem(props: SongItemProps) {
const [songDisplayItem, setSongDisplayItem] = React.useState<SongDisplayItem | undefined>(undefined); const [songDisplayItem, setSongDisplayItem] = React.useState<SongDisplayItem | undefined>(undefined);
@ -41,7 +50,13 @@ function SongItem(props: SongItemProps) {
return { return {
title: title ? title : "Unknown", title: title ? title : "Unknown",
artistNames: artistNames ? artistNames : [] artistNames: artistNames ? artistNames : [],
storeLinks: json.storeLinks.map((url: String) => {
return {
icon: getStoreIcon(url),
url: url
}
}),
}; };
}; };
@ -88,8 +103,15 @@ function ArtistItem(props: ArtistItemProps) {
const updateArtist = async () => { const updateArtist = async () => {
const response: any = await fetch(serverApi.ArtistDetailsEndpoint.replace(':id', props.id.toString())); const response: any = await fetch(serverApi.ArtistDetailsEndpoint.replace(':id', props.id.toString()));
const json: any = await response.json(); const json: any = await response.json();
return { return {
name: json.name ? json.name : "Unknown" name: json.name ? json.name : "Unknown",
storeLinks: json.storeLinks.map((url: String) => {
return {
icon: getStoreIcon(url),
url: url
}
}),
}; };
}; };

@ -59,6 +59,7 @@ export const SongDetailsEndpoint = '/song/:id';
export interface SongDetailsRequest {} export interface SongDetailsRequest {}
export interface SongDetailsResponse { export interface SongDetailsResponse {
title: String, title: String,
storeLinks: String[],
artistIds: Number[], artistIds: Number[],
albumIds: Number[], albumIds: Number[],
} }
@ -80,7 +81,8 @@ export function checkQueryArtistsRequest(req:any): boolean {
export const ArtistDetailsEndpoint = '/artist/:id'; export const ArtistDetailsEndpoint = '/artist/:id';
export interface ArtistDetailsRequest {} export interface ArtistDetailsRequest {}
export interface ArtistDetailsResponse { export interface ArtistDetailsResponse {
name: String name: String,
storeLinks: String[],
} }
export function checkArtistDetailsRequest(req:any): boolean { export function checkArtistDetailsRequest(req:any): boolean {
return true; return true;
@ -92,6 +94,7 @@ export interface CreateSongRequest {
title: String; title: String;
artistIds?: Number[]; artistIds?: Number[];
albumIds?: Number[]; albumIds?: Number[];
storeLinks?: String[];
} }
export interface CreateSongResponse { export interface CreateSongResponse {
id: Number; id: Number;
@ -107,6 +110,7 @@ export interface ModifySongRequest {
title?: String; title?: String;
artistIds?: Number[]; artistIds?: Number[];
albumIds?: Number[]; albumIds?: Number[];
storeLinks?: String[];
} }
export interface ModifySongResponse {} export interface ModifySongResponse {}
export function checkModifySongRequest(req:any): boolean { export function checkModifySongRequest(req:any): boolean {
@ -119,6 +123,7 @@ export interface CreateArtistRequest {
name: String; name: String;
songIds?: Number[]; songIds?: Number[];
albumIds?: Number[]; albumIds?: Number[];
storeLinks?: String[];
} }
export interface CreateArtistResponse { export interface CreateArtistResponse {
id: Number; id: Number;
@ -132,6 +137,7 @@ export function checkCreateArtistRequest(req:any): boolean {
export const ModifyArtistEndpoint = '/artist/:id'; export const ModifyArtistEndpoint = '/artist/:id';
export interface ModifyArtistRequest { export interface ModifyArtistRequest {
name?: String, name?: String,
storeLinks?: String[],
} }
export interface ModifyArtistResponse {} export interface ModifyArtistResponse {}
export function checkModifyArtistRequest(req:any): boolean { export function checkModifyArtistRequest(req:any): boolean {

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlnsDc="http://purl.org/dc/elements/1.1/"
xmlnsCc="http://creativecommons.org/ns#"
xmlnsRdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlnsSvg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg71"
version="1.1"
viewBox="0 0 41.804169 46.566635"
height="46.566635mm"
width="41.804169mm">
<defs
id="defs65">
<linearGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.26458333,0,0,-0.26458333,84.93125,-65.97195)"
y2="-852.20001"
y1="-826.34998"
x2="99.252998"
x1="84.329002"
id="a">
<stop
id="stop7"
offset="0"
stop-opacity="0"
stop-color="#991700" />
<stop
id="stop9"
offset="1"
stop-opacity=".1"
stop-color="#991700" />
</linearGradient>
<filter
height="1.1582"
width="1.2367001"
y="-0.079075001"
x="-0.11833"
id="b"
style="color-interpolation-filters:sRGB">
<feGaussianBlur
id="feGaussianBlur12"
stdDeviation="1.380525" />
</filter>
</defs>
<metadata
id="metadata68">
<rdfRDF>
<ccWork
rdfAAbout="">
<dcFormat>image/svg+xml</dcFormat>
<dcType
rdfResource="http://purl.org/dc/dcmitype/StillImage" />
<dcTitle></dcTitle>
</ccWork>
</rdfRDF>
</metadata>
<g
transform="translate(-84.93125,-125.55004)"
id="layer1">
<path
id="path21"
d="M 124.83042,146.22653 89.535,126.17111 c -1.508125,-0.84666 -2.8575,-0.76729 -3.677708,0.0265 -0.582084,0.55563 -0.899584,1.45521 -0.899584,2.61938 v 40.03145 c 0,1.16417 0.343959,2.06375 0.899584,2.61938 0.846666,0.82021 2.169583,0.89958 3.677708,0.0265 l 35.29542,-20.05542 c 2.54,-1.42875 2.54,-3.75708 0,-5.21229 z"
class="st0"
style="fill:#f4481e;stroke-width:0.26458332" />
<path
id="path23"
d="M 124.83042,146.49111 89.535,126.4357 c -2.54,-1.42875 -4.60375,-0.23813 -4.60375,2.64583 v -0.26458 c 0,-2.91042 2.06375,-4.10105 4.60375,-2.64584 l 35.29542,20.05542 c 1.32291,0.74083 1.95791,1.74625 1.87854,2.75167 -0.0529,-0.89959 -0.66146,-1.79917 -1.87854,-2.48709 z"
class="st2"
style="opacity:0.1;fill:#ffffff;stroke-width:0.26458332;enable-background:new" />
<path
id="path25"
d="m 121.33792,153.44965 c 0.42333,-1.4552 0.635,-3.01625 0.635,-4.60375 0,-9.33979 -7.59355,-16.93333 -16.93334,-16.93333 -9.339788,0 -16.93333,7.59354 -16.93333,16.93333 0,7.83167 5.318125,14.4198 12.54125,16.35125 z"
class="st3"
style="fill:#ffd119;stroke-width:0.26458332" />
<circle
id="circle27"
r="10.583333"
cy="148.8459"
cx="105.03958"
class="st4"
style="fill:#ff8c00;stroke-width:0.26458332" />
<path
id="path29"
d="m 105.03958,131.91257 c -9.339788,0 -16.93333,7.59354 -16.93333,16.93333 0,7.83167 5.318125,14.4198 12.54125,16.35125 l 20.69042,-11.7475 c 0.42333,-1.4552 0.635,-3.01625 0.635,-4.60375 0,-9.33979 -7.59355,-16.93333 -16.93334,-16.93333 z"
class="st5"
style="fill:url(#a);stroke-width:0.26458332" />
<g
id="g33"
transform="matrix(0.26458333,0,0,0.26458333,85.325876,125.9572)"
style="opacity:0.722;fill:#808080;filter:url(#b)">
<path
id="path31"
d="m 76,67 v 24.6 c -1.4,-0.8 -3,-1.3 -4.7,-1.3 -5.2,0 -9.3,4.2 -9.3,9.3 0,5.2 4.2,9.3 9.3,9.3 5.2,0 9.7,-4.2 9.7,-9.3 V 77 h 9 V 67 Z"
class="st6"
style="fill:#808080" />
</g>
<path
id="path35"
d="m 105.03958,143.28965 v 6.50875 c -0.37041,-0.21166 -0.79375,-0.34395 -1.24354,-0.34395 -1.37583,0 -2.46062,1.11125 -2.46062,2.46062 0,1.37583 1.11125,2.46063 2.46062,2.46063 1.37583,0 2.56646,-1.11125 2.56646,-2.46063 v -5.97958 h 2.38125 v -2.64584 z"
class="st6"
style="fill:#fafafa;stroke-width:0.26458332" />
<path
id="path37"
d="m 105.03958,132.17715 c 9.31334,0 16.88042,7.51417 16.93334,16.8275 v -0.15875 c 0,-9.33979 -7.59355,-16.93333 -16.93334,-16.93333 -9.339788,0 -16.93333,7.59354 -16.93333,16.93333 v 0.1323 c 0.07937,-9.28688 7.62,-16.80105 16.93333,-16.80105 z"
class="st7"
style="opacity:0.3;fill:#ffffff;stroke-width:0.26458332;enable-background:new" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

@ -19,6 +19,13 @@ export default function ItemListLoadedArtistItem(props: IProps) {
<ListItemText <ListItemText
primary={props.item.name} primary={props.item.name}
/> />
{props.item.storeLinks.map((link: any) => {
return <a href={link.url} target="_blank">
<ListItemIcon>
{link.icon}
</ListItemIcon>
</a>;
})}
</ListItem> </ListItem>
); );
} }

@ -25,6 +25,13 @@ export default function ItemListLoadedSongItem(props: IProps) {
primary={props.item.title} primary={props.item.title}
secondary={artists} secondary={artists}
/> />
{props.item.storeLinks.map((link: any) => {
return <a href={link.url} target="_blank">
<ListItemIcon>
{link.icon}
</ListItemIcon>
</a>;
})}
</ListItem> </ListItem>
); );
} }

@ -1,6 +1,10 @@
export interface SongDisplayItem { export interface SongDisplayItem {
title:String, title:String,
artistNames:String[], artistNames:String[],
storeLinks: {
icon: JSX.Element,
url: String,
}[]
} }
export interface LoadingSongDisplayItem { export interface LoadingSongDisplayItem {
@ -9,6 +13,10 @@ export interface LoadingSongDisplayItem {
export interface ArtistDisplayItem { export interface ArtistDisplayItem {
name:String, name:String,
storeLinks: {
icon: JSX.Element,
url: String,
}[]
} }
export interface LoadingArtistDisplayItem { export interface LoadingArtistDisplayItem {

@ -22,21 +22,35 @@ def transferLibrary(gpm_api, mudbase_api):
# Determine all unique albums per artist # Determine all unique albums per artist
artistAlbums = [ sorted(set([ song['album'] for song in songs if song['artist'] == artist ])) for artist in artists ] artistAlbums = [ sorted(set([ song['album'] for song in songs if song['artist'] == artist ])) for artist in artists ]
# Determine store ID for all artists
def getArtistStoreIds(song):
if 'artistId' in song:
return [ song['artistId'][0] ]
return [];
artistStoreIds = [ [ getArtistStoreIds(song) for song in songs if song['artist'] == artist ][0] for artist in artists ]
# Create artists and store their mudbase Ids # Create artists and store their mudbase Ids
artistMudbaseIds = [] artistMudbaseIds = []
for artist in artists: for idx,artist in enumerate(artists):
print(artistStoreIds[idx])
response = requests.post(mudbase_api + '/artist', data = { response = requests.post(mudbase_api + '/artist', data = {
'name': artist 'name': artist,
'storeLinks': [ 'https://play.google.com/music/m/' + id for id in artistStoreIds[idx] ]
}).json() }).json()
print(f"Created artist \"{artist}\", response: {response}") print(f"Created artist \"{artist}\", response: {response}")
artistMudbaseIds.append(response['id']) artistMudbaseIds.append(response['id'])
# Create songs # Create songs
def getSongStoreIds(song):
if 'storeId' in song:
return [ song['storeId'] ]
return [];
for song in songs: for song in songs:
artistMudbaseId = artistMudbaseIds[ artists.index(song['artist']) ] artistMudbaseId = artistMudbaseIds[ artists.index(song['artist']) ]
response = requests.post(mudbase_api + '/song', json = { response = requests.post(mudbase_api + '/song', json = {
'title': song['title'], 'title': song['title'],
'artistIds': [ artistMudbaseId ] 'artistIds': [ artistMudbaseId ],
'storeLinks': [ 'https://play.google.com/music/m/' + id for id in getSongStoreIds(song) ],
}).json() }).json()
print(f"Created song \"{song['title']}\" with artist ID {artistMudbaseId}, response: {response}") print(f"Created song \"{song['title']}\" with artist ID {artistMudbaseId}, response: {response}")

@ -25,8 +25,11 @@ export const ArtistDetailsEndpointHandler: EndpointHandler = async (req: any, re
throw e; throw e;
} }
let artist = artists[0]; let artist = artists[0];
const storeLinks = Array.isArray(artist.storeLinks) ? artist.storeLinks :
(artist.storeLinks ? [ artist.storeLinks ] : []);
const response: api.ArtistDetailsResponse = { const response: api.ArtistDetailsResponse = {
name: artist.name name: artist.name,
storeLinks: storeLinks,
}; };
res.send(response); res.send(response);
}) })

@ -47,6 +47,7 @@ export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res:
var song = models.Song.build({ var song = models.Song.build({
title: reqObject.title, title: reqObject.title,
storeLinks: reqObject.storeLinks || [],
}); });
artists && song.addArtists(artists); artists && song.addArtists(artists);
albums && song.addAlbums(albums); albums && song.addAlbums(albums);

@ -25,6 +25,7 @@ export const ModifyArtistEndpointHandler: EndpointHandler = async (req: any, res
} }
let artist = artists[0]; let artist = artists[0];
artist.name = reqObject.name; artist.name = reqObject.name;
if(reqObject.storeLinks) { artist.setStoreLinks(reqObject.storeLinks) };
await artist.save(); await artist.save();
}) })
.then(() => { .then(() => {

@ -77,6 +77,7 @@ export const ModifySongEndpointHandler: EndpointHandler = async (req: any, res:
if(reqObject.artistIds) { song.setArtists(artists) }; if(reqObject.artistIds) { song.setArtists(artists) };
if(reqObject.albumIds) { song.setAlbums(albums) }; if(reqObject.albumIds) { song.setAlbums(albums) };
if(reqObject.title) { song.setTitle(reqObject.title) }; if(reqObject.title) { song.setTitle(reqObject.title) };
if(reqObject.storeLinks) { song.setStoreIds(reqObject.storeLinks) };
await song.save(); await song.save();
}) })
.then(() => { .then(() => {

@ -30,6 +30,7 @@ export const SongDetailsEndpointHandler: EndpointHandler = async (req: any, res:
title: song.title, title: song.title,
artistIds: song.Artists.map((artist:any) => artist.id), artistIds: song.Artists.map((artist:any) => artist.id),
albumIds: song.Albums.map((album:any) => album.id), albumIds: song.Albums.map((album:any) => album.id),
storeLinks: song.storeLinks,
} }
res.send(response); res.send(response);
}) })

@ -1,6 +1,7 @@
module.exports = (sequelize, DataTypes) => { module.exports = (sequelize, DataTypes) => {
var Album = sequelize.define('Album', { var Album = sequelize.define('Album', {
name: DataTypes.STRING, name: DataTypes.STRING,
storeLinks: DataTypes.JSON,
}); });
Album.associate = function (models) { Album.associate = function (models) {

@ -1,6 +1,7 @@
module.exports = (sequelize, DataTypes) => { module.exports = (sequelize, DataTypes) => {
var Artist = sequelize.define('Artist', { var Artist = sequelize.define('Artist', {
name: DataTypes.STRING, name: DataTypes.STRING,
storeLinks: DataTypes.JSON,
}); });
Artist.associate = function (models) { Artist.associate = function (models) {

@ -1,6 +1,7 @@
module.exports = (sequelize, DataTypes) => { module.exports = (sequelize, DataTypes) => {
var Song = sequelize.define('Song', { var Song = sequelize.define('Song', {
title: DataTypes.STRING, title: DataTypes.STRING,
storeLinks: DataTypes.JSON,
}); });
Song.associate = function (models) { Song.associate = function (models) {

Loading…
Cancel
Save