Page Menu
Home
desp's stash
Search
Configure Global Search
Log In
Files
F349488
SpotifyAudioSourceManager.java
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
11 KB
Subscribers
None
SpotifyAudioSourceManager.java
View Options
package
me.despawningbone.discordbot.command.music
;
import
java.io.DataInput
;
import
java.io.DataOutput
;
import
java.io.IOException
;
import
java.net.MalformedURLException
;
import
java.net.URI
;
import
java.net.URISyntaxException
;
import
java.net.URL
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.file.Path
;
import
java.nio.file.Paths
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.concurrent.TimeUnit
;
import
java.util.stream.Collectors
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.hc.core5.http.ParseException
;
import
org.apache.http.NameValuePair
;
import
org.apache.http.client.utils.URLEncodedUtils
;
import
org.awaitility.Awaitility
;
import
com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager
;
import
com.sedmelluq.discord.lavaplayer.source.AudioSourceManager
;
import
com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager
;
import
com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack
;
import
com.sedmelluq.discord.lavaplayer.track.AudioItem
;
import
com.sedmelluq.discord.lavaplayer.track.AudioPlaylist
;
import
com.sedmelluq.discord.lavaplayer.track.AudioReference
;
import
com.sedmelluq.discord.lavaplayer.track.AudioTrack
;
import
com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo
;
import
com.sedmelluq.discord.lavaplayer.track.BasicAudioPlaylist
;
import
com.wrapper.spotify.SpotifyApi
;
import
com.wrapper.spotify.enums.ModelObjectType
;
import
com.wrapper.spotify.exceptions.SpotifyWebApiException
;
import
com.wrapper.spotify.exceptions.detailed.NotFoundException
;
import
com.wrapper.spotify.model_objects.credentials.ClientCredentials
;
import
com.wrapper.spotify.model_objects.specification.Album
;
import
com.wrapper.spotify.model_objects.specification.Paging
;
import
com.wrapper.spotify.model_objects.specification.Playlist
;
import
com.wrapper.spotify.model_objects.specification.PlaylistTrack
;
import
com.wrapper.spotify.model_objects.specification.Track
;
import
com.wrapper.spotify.model_objects.specification.TrackSimplified
;
import
com.wrapper.spotify.requests.data.albums.GetAlbumsTracksRequest
;
import
com.wrapper.spotify.requests.data.playlists.GetPlaylistsItemsRequest
;
/**
* Code mostly modified from https://github.com/lijamez/tonbot-plugin-music/
* @author lijamez @ github
*/
public
class
SpotifyAudioSourceManager
implements
AudioSourceManager
{
private
static
final
String
SPOTIFY_DOMAIN
=
"open.spotify.com"
;
private
static
final
int
EXPECTED_PATH_COMPONENTS
=
2
;
private
SpotifyApi
spotifyApi
;
private
YoutubeAudioSourceManager
manager
;
public
SpotifyAudioSourceManager
(
SpotifyApi
spotifyApi
,
AudioPlayerManager
parent
,
AudioTrackHandler
handler
)
throws
Exception
{
this
.
spotifyApi
=
spotifyApi
;
handler
.
ex
.
submit
(()
->
{
Awaitility
.
await
().
until
(()
->
parent
.
source
(
YoutubeAudioSourceManager
.
class
)
!=
null
);
//needed to ensure its loaded
this
.
manager
=
parent
.
source
(
YoutubeAudioSourceManager
.
class
);
});
refreshSpotifyApi
(
handler
);
}
private
void
refreshSpotifyApi
(
AudioTrackHandler
handler
)
throws
Exception
{
ClientCredentials
cred
=
spotifyApi
.
clientCredentials
().
build
().
execute
();
spotifyApi
.
setAccessToken
(
cred
.
getAccessToken
());
handler
.
ex
.
schedule
(()
->
{
try
{
refreshSpotifyApi
(
handler
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
//DONE disable the source manager?
spotifyApi
=
null
;
}
},
cred
.
getExpiresIn
(),
TimeUnit
.
SECONDS
);
}
@Override
public
String
getSourceName
()
{
return
"Spotify Playlist"
;
}
@Override
public
AudioItem
loadItem
(
AudioPlayerManager
manager
,
AudioReference
reference
)
{
if
(
spotifyApi
==
null
)
return
null
;
//disabled due to broken api
try
{
URL
url
=
new
URL
(
reference
.
identifier
);
if
(!
StringUtils
.
equals
(
url
.
getHost
(),
SPOTIFY_DOMAIN
))
{
return
null
;
}
AudioItem
audioItem
=
null
;
audioItem
=
handleAsPlaylist
(
url
,
manager
);
if
(
audioItem
==
null
)
{
audioItem
=
handleAsTrack
(
url
,
manager
);
}
return
audioItem
;
}
catch
(
MalformedURLException
e
)
{
return
null
;
}
}
private
AudioTrack
handleAsTrack
(
URL
url
,
AudioPlayerManager
man
)
{
Path
path
=
Paths
.
get
(
url
.
getPath
());
if
(
path
.
getNameCount
()
<
2
)
{
return
null
;
}
if
(!
StringUtils
.
equals
(
path
.
getName
(
0
).
toString
(),
"track"
))
{
return
null
;
}
String
trackId
=
path
.
getName
(
1
).
toString
();
TrackSimplified
track
;
try
{
Track
t
=
spotifyApi
.
getTrack
(
trackId
).
build
().
execute
();
track
=
new
TrackSimplified
.
Builder
().
setArtists
(
t
.
getArtists
()).
setName
(
t
.
getName
()).
setDurationMs
(
t
.
getDurationMs
()).
build
();
}
catch
(
IOException
|
SpotifyWebApiException
|
ParseException
e
)
{
throw
new
IllegalStateException
(
"Unable to fetch track from Spotify API."
,
e
);
}
return
getAudioTracks
(
Arrays
.
asList
(
track
),
man
).
get
(
0
);
}
private
BasicAudioPlaylist
handleAsPlaylist
(
URL
url
,
AudioPlayerManager
man
)
{
String
playlistKey
;
try
{
playlistKey
=
extractPlaylistId
(
url
);
}
catch
(
IllegalArgumentException
e
)
{
return
null
;
}
String
name
;
List
<
TrackSimplified
>
tracks
;
try
{
Playlist
playlist
=
spotifyApi
.
getPlaylist
(
playlistKey
)
.
build
().
execute
();
name
=
playlist
.
getName
();
tracks
=
getAllPlaylistTracks
(
playlist
).
stream
().
map
(
pt
->
pt
.
getTrack
())
.
filter
(
pt
->
pt
.
getType
()
==
ModelObjectType
.
TRACK
).
map
(
pt
->
{
Track
t
=
(
Track
)
pt
;
//it doesnt have any methods to translate Track to TrackSimplified, so i had to do this; it only uses 3 params anyways
return
new
TrackSimplified
.
Builder
().
setArtists
(
t
.
getArtists
()).
setName
(
t
.
getName
()).
setDurationMs
(
t
.
getDurationMs
()).
build
();
}).
collect
(
Collectors
.
toList
());
}
catch
(
IOException
|
SpotifyWebApiException
|
ParseException
e
)
{
if
(
e
instanceof
NotFoundException
)
{
//try searching as album
try
{
Album
album
=
spotifyApi
.
getAlbum
(
playlistKey
).
build
().
execute
();
name
=
album
.
getName
();
tracks
=
getAllAlbumTracks
(
album
);
}
catch
(
ParseException
|
SpotifyWebApiException
|
IOException
e1
)
{
throw
new
IllegalStateException
(
"Unable to fetch playlist from Spotify API."
,
e1
);
}
}
else
{
throw
new
IllegalStateException
(
"Unable to fetch playlist from Spotify API."
,
e
);
}
}
List
<
AudioTrack
>
audioTracks
=
getAudioTracks
(
tracks
,
man
);
return
new
BasicAudioPlaylist
(
name
,
audioTracks
,
null
,
false
);
}
private
List
<
PlaylistTrack
>
getAllPlaylistTracks
(
Playlist
playlist
)
{
List
<
PlaylistTrack
>
playlistTracks
=
new
ArrayList
<>();
Paging
<
PlaylistTrack
>
currentPage
=
playlist
.
getTracks
();
do
{
playlistTracks
.
addAll
(
Arrays
.
asList
(
currentPage
.
getItems
()));
if
(
currentPage
.
getNext
()
==
null
)
{
currentPage
=
null
;
}
else
{
try
{
URI
nextPageUri
=
new
URI
(
currentPage
.
getNext
());
List
<
NameValuePair
>
queryPairs
=
URLEncodedUtils
.
parse
(
nextPageUri
,
StandardCharsets
.
UTF_8
);
GetPlaylistsItemsRequest
.
Builder
b
=
spotifyApi
.
getPlaylistsItems
(
playlist
.
getId
());
for
(
NameValuePair
queryPair
:
queryPairs
)
{
b
=
b
.
setBodyParameter
(
queryPair
.
getName
(),
queryPair
.
getValue
());
}
currentPage
=
b
.
build
().
execute
();
}
catch
(
IOException
|
SpotifyWebApiException
|
ParseException
e
)
{
throw
new
IllegalStateException
(
"Unable to query Spotify for playlist tracks."
,
e
);
}
catch
(
URISyntaxException
e
)
{
throw
new
IllegalStateException
(
"Spotify returned an invalid 'next page' URI."
,
e
);
}
}
}
while
(
currentPage
!=
null
);
return
playlistTracks
;
}
private
List
<
TrackSimplified
>
getAllAlbumTracks
(
Album
album
)
{
List
<
TrackSimplified
>
albumTracks
=
new
ArrayList
<>();
Paging
<
TrackSimplified
>
currentPage
=
album
.
getTracks
();
do
{
albumTracks
.
addAll
(
Arrays
.
asList
(
currentPage
.
getItems
()));
if
(
currentPage
.
getNext
()
==
null
)
{
currentPage
=
null
;
}
else
{
try
{
URI
nextPageUri
=
new
URI
(
currentPage
.
getNext
());
List
<
NameValuePair
>
queryPairs
=
URLEncodedUtils
.
parse
(
nextPageUri
,
StandardCharsets
.
UTF_8
);
GetAlbumsTracksRequest
.
Builder
b
=
spotifyApi
.
getAlbumsTracks
(
album
.
getId
());
for
(
NameValuePair
queryPair
:
queryPairs
)
{
b
=
b
.
setBodyParameter
(
queryPair
.
getName
(),
queryPair
.
getValue
());
}
currentPage
=
b
.
build
().
execute
();
}
catch
(
IOException
|
SpotifyWebApiException
|
ParseException
e
)
{
throw
new
IllegalStateException
(
"Unable to query Spotify for album tracks."
,
e
);
}
catch
(
URISyntaxException
e
)
{
throw
new
IllegalStateException
(
"Spotify returned an invalid 'next page' URI."
,
e
);
}
}
}
while
(
currentPage
!=
null
);
return
albumTracks
;
}
private
String
extractPlaylistId
(
URL
url
)
{
Path
path
=
Paths
.
get
(
url
.
getPath
());
if
(
path
.
getNameCount
()
<
EXPECTED_PATH_COMPONENTS
)
{
throw
new
IllegalArgumentException
(
"Not enough path components."
);
}
if
(!
Arrays
.
asList
(
"playlist"
,
"album"
).
contains
(
path
.
getName
(
0
).
toString
()))
{
throw
new
IllegalArgumentException
(
"URL doesn't appear to be a playlist."
);
}
String
playlistId
=
path
.
getName
(
1
).
toString
();
if
(
StringUtils
.
isBlank
(
playlistId
))
{
throw
new
IllegalArgumentException
(
"Playlist ID is blank."
);
}
return
playlistId
;
}
@Override
public
boolean
isTrackEncodable
(
AudioTrack
track
)
{
return
false
;
}
@Override
public
void
encodeTrack
(
AudioTrack
track
,
DataOutput
output
)
throws
IOException
{
throw
new
UnsupportedOperationException
(
"encodeTrack is unsupported."
);
}
@Override
public
AudioTrack
decodeTrack
(
AudioTrackInfo
trackInfo
,
DataInput
input
)
throws
IOException
{
throw
new
UnsupportedOperationException
(
"decodeTrack is unsupported."
);
}
@Override
public
void
shutdown
()
{
}
private
List
<
AudioTrack
>
getAudioTracks
(
List
<
TrackSimplified
>
tracks
,
AudioPlayerManager
manager
)
{
return
tracks
.
parallelStream
().
map
(
track
->
{
//parallelStream made a world of difference in loading times lmao
String
artist
=
track
.
getArtists
().
length
<
1
?
""
:
track
.
getArtists
()[
0
].
getName
();
AudioItem
item
=
this
.
manager
.
loadItem
(
manager
,
new
AudioReference
(
"ytsearch:"
+
artist
+
" "
+
track
.
getName
(),
null
));
if
(
item
instanceof
AudioPlaylist
)
{
AudioPlaylist
audioPlaylist
=
(
AudioPlaylist
)
item
;
// The number of matches is limited to reduce the chances of matching against
// less than optimal results.
// The best match is the one that has the smallest track duration delta.
YoutubeAudioTrack
bestMatch
=
audioPlaylist
.
getTracks
().
stream
().
limit
(
3
)
.
map
(
t
->
(
YoutubeAudioTrack
)
t
).
min
((
o1
,
o2
)
->
{
long
o1TimeDelta
=
Math
.
abs
(
o1
.
getDuration
()
-
track
.
getDurationMs
());
long
o2TimeDelta
=
Math
.
abs
(
o2
.
getDuration
()
-
track
.
getDurationMs
());
return
(
int
)
(
o1TimeDelta
-
o2TimeDelta
);
}).
orElse
(
null
);
return
bestMatch
;
}
else
if
(
item
instanceof
YoutubeAudioTrack
)
{
return
(
YoutubeAudioTrack
)
item
;
}
else
if
(
item
instanceof
AudioReference
)
{
//no results; retry once more
System
.
out
.
println
(
"Spotify Source Manager: Retry needed for "
+
track
.
getName
());
item
=
this
.
manager
.
loadItem
(
manager
,
new
AudioReference
(
"ytsearch:"
+
artist
+
" "
+
track
.
getName
(),
null
));
if
(
item
instanceof
AudioPlaylist
)
item
=
((
AudioPlaylist
)
item
).
getTracks
().
get
(
0
);
//cba doing the best match lmao
else
if
(!(
item
instanceof
YoutubeAudioTrack
))
return
null
;
//if not playlist and track return null again
return
(
YoutubeAudioTrack
)
item
;
}
else
{
throw
new
IllegalArgumentException
(
"Unknown AudioItem"
);
//should never throw
}
}).
collect
(
Collectors
.
toList
());
}
}
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Thu, May 8, 5:50 AM (9 h, 15 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
8f/b5/613b1080f21dc71b415124bff7dc
Attached To
rDESB despbot
Event Timeline
Log In to Comment