66import de .labystudio .spotifyapi .model .Track ;
77import de .labystudio .spotifyapi .open .model .AccessTokenResponse ;
88import de .labystudio .spotifyapi .open .model .track .OpenTrack ;
9- import de .labystudio .spotifyapi .open .util .TOTP ;
9+ import de .labystudio .spotifyapi .open .totp .Secret ;
10+ import de .labystudio .spotifyapi .open .totp .SecretFetcher ;
11+ import de .labystudio .spotifyapi .open .totp .TOTP ;
1012
1113import javax .imageio .ImageIO ;
1214import javax .net .ssl .HttpsURLConnection ;
3032 */
3133public class OpenSpotifyAPI {
3234
33- private static final Gson GSON = new Gson ();
35+ public static final Gson GSON = new Gson ();
3436
35- private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64 ) Chrome/71 .0.3578 ." + (int ) (Math .random () * 90 );
36- private static final String URL_API_GEN_ACCESS_TOKEN = "https://open.spotify.com/api/token?reason=%s&productType=web-player&totp=%s&totpVer=5 " ;
37+ public static final String USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko ) Chrome/138 .0.0.0 Safari/537 ." + (int ) (Math .random () * 90 );
38+ public static final String URL_API_GEN_ACCESS_TOKEN = "https://open.spotify.com/api/token?reason=%s&productType=web-player&totp=%s&totpServer=%s& totpVer=%s " ;
3739
38- private static final String URL_API_TRACKS = "https://api.spotify.com/v1/tracks/%s" ;
39- private static final String URL_API_SERVER_TIME = "https://open.spotify.com/api/server-time" ;
40-
41- public static final int [] TOTP_SECRET = {12 , 56 , 76 , 33 , 88 , 44 , 88 , 33 , 78 , 78 , 11 , 66 , 22 , 22 , 55 , 69 , 54 };
40+ public static final String URL_API_TRACKS = "https://api.spotify.com/v1/tracks/%s" ;
41+ public static final String URL_API_SERVER_TIME = "https://open.spotify.com/api/server-time" ;
4242
4343 private final Executor executor = Executors .newSingleThreadExecutor ();
4444
@@ -72,8 +72,8 @@ public long requestServerTime() throws IOException {
7272 HttpURLConnection conn = (HttpURLConnection ) url .openConnection ();
7373 conn .setRequestMethod ("GET" );
7474 conn .setRequestProperty ("Host" , "open.spotify.com" );
75- conn .setRequestProperty ("User-Agent" , "Mozilla/5.0" );
76- conn .setRequestProperty ("accept " , "*/* " );
75+ conn .setRequestProperty ("User-Agent" , USER_AGENT );
76+ conn .setRequestProperty ("Accept " , "application/json " );
7777
7878 BufferedReader reader = new BufferedReader (new InputStreamReader (conn .getInputStream ()));
7979 String response = reader .readLine ();
@@ -83,46 +83,19 @@ public long requestServerTime() throws IOException {
8383 return obj .get ("serverTime" ).getAsLong ();
8484 }
8585
86- /**
87- * Generate time-based one time password
88- *
89- * @param serverTime server time in seconds
90- * @return 6 digits one time password
91- */
92- public String generateTotp (long serverTime ) {
93- // Convert secret numbers to xor results
94- StringBuilder xorResults = new StringBuilder ();
95- for (int i = 0 ; i < TOTP_SECRET .length ; i ++) {
96- int result = TOTP_SECRET [i ] ^ (i % 33 + 9 );
97- xorResults .append (result );
98- }
99-
100- // Convert xor results to hex
101- StringBuilder hexResult = new StringBuilder ();
102- for (int i = 0 ; i < xorResults .length (); i ++) {
103- hexResult .append (String .format ("%02x" , (int ) xorResults .charAt (i )));
104- }
105-
106- // Convert hex to byte array
107- byte [] byteArray = new byte [hexResult .length () / 2 ];
108- for (int i = 0 ; i < hexResult .length (); i += 2 ) {
109- int byteValue = Integer .parseInt (hexResult .substring (i , i + 2 ), 16 );
110- byteArray [i / 2 ] = (byte ) byteValue ;
111- }
112- return TOTP .generateOtp (byteArray , serverTime , 30 , 6 );
113- }
114-
11586 /**
11687 * Generate an access token for the open spotify api
11788 */
11889 private AccessTokenResponse generateAccessToken () throws IOException {
90+ SecretFetcher secretFetcher = new SecretFetcher ();
91+ Secret secret = secretFetcher .fetchLatest ();
11992 long serverTime = this .requestServerTime ();
120- String totp = this . generateTotp ( serverTime );
93+ String totp = TOTP . generateOtp ( secret . toBytes (), serverTime , 30 , 6 );
12194
122- AccessTokenResponse response = this .getToken ("transport" , totp );
95+ AccessTokenResponse response = this .getToken ("transport" , totp , secret . getVersion () );
12396
12497 if (!this .hasValidAccessToken (response )) {
125- response = this .getToken ("init" , totp );
98+ response = this .getToken ("init" , totp , secret . getVersion () );
12699 }
127100
128101 if (!this .hasValidAccessToken (response )) {
@@ -135,13 +108,14 @@ private AccessTokenResponse generateAccessToken() throws IOException {
135108 /**
136109 * Retrieve access token using totp
137110 */
138- private AccessTokenResponse getToken (String mode , String totp ) throws IOException {
111+ private AccessTokenResponse getToken (String mode , String totp , int version ) throws IOException {
139112 // Open connection
140- String url = String .format (URL_API_GEN_ACCESS_TOKEN , mode , totp );
113+ String url = String .format (URL_API_GEN_ACCESS_TOKEN , mode , totp , totp , version );
141114 HttpsURLConnection connection = (HttpsURLConnection ) new URL (url ).openConnection ();
142115 connection .addRequestProperty ("User-Agent" , USER_AGENT );
143116 connection .addRequestProperty ("referer" , "https://open.spotify.com/" );
144117 connection .addRequestProperty ("app-platform" , "WebPlayer" );
118+ connection .setRequestProperty ("Accept" , "application/json" );
145119
146120 int code = connection .getResponseCode ();
147121 if (code != HttpURLConnection .HTTP_OK ) {
0 commit comments