Skip to content

Commit 55eb953

Browse files
authored
Merge pull request #37 from adamint/molikuner-dev
Molikuner dev
2 parents 8988a72 + 60ad384 commit 55eb953

File tree

6 files changed

+212
-95
lines changed

6 files changed

+212
-95
lines changed

build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ repositories {
2121
dependencies {
2222
// Actual library dependencies
2323
implementation(group: 'org.json', name: 'json', version: '20180130')
24-
implementation('org.jsoup:jsoup:1.10.3')
2524
implementation("com.google.code.gson:gson:2.8.1")
2625

2726
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.11"

src/main/kotlin/com/adamratzman/spotify/main/SpotifyAPI.kt

Lines changed: 67 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ import com.adamratzman.spotify.endpoints.public.PlaylistsAPI
1515
import com.adamratzman.spotify.endpoints.public.SearchAPI
1616
import com.adamratzman.spotify.endpoints.public.TracksAPI
1717
import com.adamratzman.spotify.endpoints.public.UserAPI
18+
import com.adamratzman.spotify.utils.HttpConnection
19+
import com.adamratzman.spotify.utils.HttpHeader
20+
import com.adamratzman.spotify.utils.HttpRequestMethod
1821
import com.adamratzman.spotify.utils.SpotifyEndpoint
1922
import com.adamratzman.spotify.utils.Token
2023
import com.adamratzman.spotify.utils.byteEncode
2124
import com.adamratzman.spotify.utils.toObject
2225
import com.google.gson.Gson
2326
import com.google.gson.GsonBuilder
24-
import org.jsoup.Jsoup
2527
import java.util.concurrent.Executors
2628
import java.util.concurrent.TimeUnit
2729

@@ -36,26 +38,17 @@ class SpotifyApiBuilderJava(val clientId: String, val clientSecret: String) {
3638
var authorizationCode: String? = null
3739
var tokenString: String? = null
3840
var token: Token? = null
41+
var useCache: Boolean = true
3942

40-
fun redirectUri(redirectUri: String?): SpotifyApiBuilderJava {
41-
this.redirectUri = redirectUri
42-
return this
43-
}
43+
fun useCache(useCache: Boolean) = apply { this.useCache = useCache }
4444

45-
fun authorizationCode(authorizationCode: String?): SpotifyApiBuilderJava {
46-
this.authorizationCode = authorizationCode
47-
return this
48-
}
45+
fun redirectUri(redirectUri: String?) = apply { this.redirectUri = redirectUri }
4946

50-
fun tokenString(tokenString: String?): SpotifyApiBuilderJava {
51-
this.tokenString = tokenString
52-
return this
53-
}
47+
fun authorizationCode(authorizationCode: String?) = apply { this.authorizationCode = authorizationCode }
5448

55-
fun token(token: Token?): SpotifyApiBuilderJava {
56-
this.token = token
57-
return this
58-
}
49+
fun tokenString(tokenString: String?) = apply { this.tokenString = tokenString }
50+
51+
fun token(token: Token?) = apply { this.token = token }
5952

6053
fun buildCredentialed() = spotifyApi {
6154
credentials {
@@ -118,6 +111,7 @@ class SpotifyUserAuthorizationBuilder(
118111
class SpotifyApiBuilder {
119112
private var credentials: SpotifyCredentials = SpotifyCredentials(null, null, null)
120113
private var authentication = SpotifyUserAuthorizationBuilder()
114+
var useCache: Boolean = true
121115

122116
fun credentials(block: SpotifyCredentialsBuilder.() -> Unit) {
123117
credentials = SpotifyCredentialsBuilder().apply(block).build()
@@ -138,6 +132,8 @@ class SpotifyApiBuilder {
138132
return getAuthUrlFull(*scopes, clientId = credentials.clientId!!, redirectUri = credentials.redirectUri!!)
139133
}
140134

135+
fun buildCredentialedAsync(consumer: (SpotifyAPI) -> Unit) = Runnable { consumer(buildCredentialed()) }.run()
136+
141137
fun buildCredentialed(): SpotifyAPI {
142138
val clientId = credentials.clientId
143139
val clientSecret = credentials.clientSecret
@@ -146,7 +142,7 @@ class SpotifyApiBuilder {
146142
}
147143
return when {
148144
authentication.token != null -> {
149-
SpotifyAppAPI(clientId ?: "not-set", clientSecret ?: "not-set", authentication.token!!)
145+
SpotifyAppAPI(clientId ?: "not-set", clientSecret ?: "not-set", authentication.token!!, useCache)
150146
}
151147
authentication.tokenString != null -> {
152148
SpotifyAppAPI(
@@ -155,27 +151,24 @@ class SpotifyApiBuilder {
155151
Token(
156152
authentication.tokenString!!, "client_credentials",
157153
60000, null, null
158-
)
154+
),
155+
useCache
159156
)
160157
}
161158
else -> try {
162-
val token = Gson().fromJson(
163-
Jsoup.connect("https://accounts.spotify.com/api/token")
164-
.data("grant_type", "client_credentials")
165-
.header("Authorization", "Basic " + ("$clientId:$clientSecret".byteEncode()))
166-
.ignoreContentType(true).post().body().text(), Token::class.java
167-
) ?: throw IllegalArgumentException("Invalid credentials provided")
168-
SpotifyAppAPI(
169-
clientId ?: throw IllegalArgumentException(),
170-
clientSecret ?: throw IllegalArgumentException(),
171-
token
172-
)
159+
if (clientId == null || clientSecret == null) throw IllegalArgumentException("Illegal credentials provided")
160+
val token = getCredentialedToken(clientId, clientSecret)
161+
?: throw IllegalArgumentException("Invalid credentials provided")
162+
SpotifyAppAPI(clientId, clientSecret, token, useCache)
173163
} catch (e: Exception) {
174164
throw SpotifyException("Invalid credentials provided in the login process", e)
175165
}
176166
}
177167
}
178168

169+
fun buildClientAsync(consumer: (SpotifyClientAPI) -> Unit, automaticRefresh: Boolean = false) =
170+
Runnable { consumer(buildClient(automaticRefresh)) }.run()
171+
179172
fun buildClient(automaticRefresh: Boolean = false): SpotifyClientAPI =
180173
buildClient(
181174
authentication.authorizationCode, authentication.tokenString,
@@ -210,14 +203,20 @@ class SpotifyApiBuilder {
210203
SpotifyClientAPI(
211204
clientId ?: throw IllegalArgumentException(),
212205
clientSecret ?: throw IllegalArgumentException(),
213-
Jsoup.connect("https://accounts.spotify.com/api/token")
214-
.data("grant_type", "authorization_code")
215-
.data("code", authorizationCode)
216-
.data("redirect_uri", redirectUri)
217-
.header("Authorization", "Basic " + ("$clientId:$clientSecret").byteEncode())
218-
.ignoreContentType(true).post().body().text().toObject(Gson(), Token::class.java),
206+
HttpConnection(
207+
url = "https://accounts.spotify.com/api/token",
208+
method = HttpRequestMethod.POST,
209+
body = "grant_type=authorization_code&code=$authorizationCode&redirect_uri=$redirectUri",
210+
contentType = "application/x-www-form-urlencoded"
211+
).execute(
212+
HttpHeader(
213+
"Authorization",
214+
"Basic ${"$clientId:$clientSecret".byteEncode()}"
215+
)
216+
).body.toObject(Gson(), Token::class.java),
219217
automaticRefresh,
220-
redirectUri ?: throw IllegalArgumentException()
218+
redirectUri ?: throw IllegalArgumentException(),
219+
useCache
221220
)
222221
} catch (e: Exception) {
223222
throw SpotifyException("Invalid credentials provided in the login process", e)
@@ -227,13 +226,15 @@ class SpotifyApiBuilder {
227226
clientSecret ?: "not-set",
228227
token,
229228
automaticRefresh,
230-
redirectUri ?: "not-set"
229+
redirectUri ?: "not-set",
230+
useCache
231231
)
232232
tokenString != null -> SpotifyClientAPI(
233233
clientId ?: "not-set", clientSecret ?: "not-set", Token(
234234
tokenString, "client_credentials", 1000,
235235
null, null
236-
), false, redirectUri ?: "not-set"
236+
), false, redirectUri ?: "not-set",
237+
useCache
237238
)
238239
else -> throw IllegalArgumentException(
239240
"At least one of: authorizationCode, tokenString, or token must be provided " +
@@ -243,7 +244,10 @@ class SpotifyApiBuilder {
243244
}
244245
}
245246

246-
abstract class SpotifyAPI internal constructor(val clientId: String, val clientSecret: String, var token: Token) {
247+
abstract class SpotifyAPI internal constructor(
248+
val clientId: String, val clientSecret: String,
249+
var token: Token, var useCache: Boolean
250+
) {
247251
internal var expireTime = System.currentTimeMillis() + token.expires_in * 1000
248252
internal val executor = Executors.newScheduledThreadPool(2)
249253
internal val gson = GsonBuilder().setLenient().create()!!
@@ -262,8 +266,6 @@ abstract class SpotifyAPI internal constructor(val clientId: String, val clientS
262266
abstract fun refreshToken()
263267
abstract fun clearCache()
264268

265-
var useCache: Boolean = true
266-
267269
init {
268270
executor.scheduleAtFixedRate(::clearCache, 10, 10, TimeUnit.MINUTES)
269271
}
@@ -281,8 +283,8 @@ abstract class SpotifyAPI internal constructor(val clientId: String, val clientS
281283
}
282284
}
283285

284-
class SpotifyAppAPI internal constructor(clientId: String, clientSecret: String, token: Token) :
285-
SpotifyAPI(clientId, clientSecret, token) {
286+
class SpotifyAppAPI internal constructor(clientId: String, clientSecret: String, token: Token, useCache: Boolean) :
287+
SpotifyAPI(clientId, clientSecret, token, useCache) {
286288
override val search: SearchAPI = SearchAPI(this)
287289
override val albums: AlbumAPI = AlbumAPI(this)
288290
override val browse: BrowseAPI = BrowseAPI(this)
@@ -300,12 +302,7 @@ class SpotifyAppAPI internal constructor(clientId: String, clientSecret: String,
300302

301303
override fun refreshToken() {
302304
if (clientId != "not-set" && clientSecret != "not-set")
303-
token = gson.fromJson(
304-
Jsoup.connect("https://accounts.spotify.com/api/token")
305-
.data("grant_type", "client_credentials")
306-
.header("Authorization", "Basic " + ("$clientId:$clientSecret".byteEncode()))
307-
.ignoreContentType(true).post().body().text(), Token::class.java
308-
)
305+
token = getCredentialedToken(clientId, clientSecret)
309306
expireTime = System.currentTimeMillis() + token.expires_in * 1000
310307
}
311308

@@ -326,8 +323,9 @@ class SpotifyClientAPI internal constructor(
326323
clientSecret: String,
327324
token: Token,
328325
automaticRefresh: Boolean = false,
329-
var redirectUri: String
330-
) : SpotifyAPI(clientId, clientSecret, token) {
326+
var redirectUri: String,
327+
useCache: Boolean
328+
) : SpotifyAPI(clientId, clientSecret, token, useCache) {
331329
override val search: SearchAPI = SearchAPI(this)
332330
override val albums: AlbumAPI = AlbumAPI(this)
333331
override val browse: BrowseAPI = BrowseAPI(this)
@@ -372,11 +370,13 @@ class SpotifyClientAPI internal constructor(
372370

373371
override fun refreshToken() {
374372
val tempToken = gson.fromJson(
375-
Jsoup.connect("https://accounts.spotify.com/api/token")
376-
.data("grant_type", "refresh_token")
377-
.data("refresh_token", token.refresh_token ?: "")
378-
.header("Authorization", "Basic " + ("$clientId:$clientSecret").byteEncode())
379-
.ignoreContentType(true).post().body().text(), Token::class.java
373+
HttpConnection(
374+
url = "https://accounts.spotify.com/api/token",
375+
method = HttpRequestMethod.POST,
376+
body = "grant_type=refresh_token&refresh_token=${token.refresh_token ?: ""}",
377+
contentType = "application/x-www-form-urlencoded"
378+
).execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}")).body,
379+
Token::class.java
380380
)
381381
if (tempToken == null) {
382382
logger.logWarning("Spotify token refresh failed")
@@ -411,3 +411,13 @@ private fun getAuthUrlFull(vararg scopes: SpotifyScope, clientId: String, redire
411411
"&redirect_uri=$redirectUri" +
412412
if (scopes.isEmpty()) "" else "&scope=${scopes.joinToString("%20") { it.uri }}"
413413
}
414+
415+
private fun getCredentialedToken(clientId: String, clientSecret: String) = Gson().fromJson(
416+
HttpConnection(
417+
url = "https://accounts.spotify.com/api/token",
418+
method = HttpRequestMethod.POST,
419+
body = "grant_type=client_credentials",
420+
contentType = "application/x-www-form-urlencoded"
421+
).execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}")).body,
422+
Token::class.java
423+
)

src/main/kotlin/com/adamratzman/spotify/utils/Endpoints.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ import java.util.function.Supplier
1313
abstract class SpotifyEndpoint(val api: SpotifyAPI) {
1414
internal val cache = SpotifyCache()
1515

16-
fun get(url: String): String {
16+
internal fun get(url: String): String {
1717
return execute(url)
1818
}
1919

20-
fun post(url: String, body: String? = null): String {
20+
internal fun post(url: String, body: String? = null): String {
2121
return execute(url, body, HttpRequestMethod.POST)
2222
}
2323

24-
fun put(url: String, body: String? = null, contentType: String? = null): String {
24+
internal fun put(url: String, body: String? = null, contentType: String? = null): String {
2525
return execute(url, body, HttpRequestMethod.PUT, contentType = contentType)
2626
}
2727

28-
fun delete(
28+
internal fun delete(
2929
url: String,
3030
body: String? = null,
3131
contentType: String? = null
@@ -50,9 +50,11 @@ abstract class SpotifyEndpoint(val api: SpotifyAPI) {
5050
cache -= spotifyRequest
5151
}
5252

53-
val document = createConnection(url, body, method, contentType).apply {
54-
if (cacheState?.eTag != null) headers.add(HttpHeader("If-None-Match", cacheState.eTag))
55-
}.execute()
53+
val document = createConnection(url, body, method, contentType).execute(
54+
cacheState?.eTag?.let {
55+
HttpHeader("If-None-Match", it)
56+
}
57+
)
5658

5759
return handleResponse(document, cacheState, spotifyRequest, retry202) ?: execute(
5860
url,

src/main/kotlin/com/adamratzman/spotify/utils/HttpConnection.kt

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,44 @@ package com.adamratzman.spotify.utils
33
import java.net.HttpURLConnection
44
import java.net.URL
55

6-
enum class HttpRequestMethod { GET, POST, PUT, DELETE, }
6+
enum class HttpRequestMethod { GET, POST, PUT, DELETE }
77
data class HttpHeader(val key: String, val value: String)
88

99
class HttpConnection(
1010
private val url: String,
1111
private val method: HttpRequestMethod,
1212
private val body: String?,
1313
private val contentType: String?,
14-
vararg headersVararg: HttpHeader,
15-
val headers: MutableList<HttpHeader> = headersVararg.toMutableList()
14+
private vararg val headers: HttpHeader
1615
) {
1716

18-
fun execute(): HttpResponse {
17+
fun execute(vararg additionalHeaders: HttpHeader?): HttpResponse {
1918
val connection = URL(url).openConnection() as HttpURLConnection
2019
connection.requestMethod = method.toString()
2120

2221
contentType?.let { connection.setRequestProperty("Content-Type", contentType) }
23-
headers.forEach { (key, value) -> connection.setRequestProperty(key, value) }
24-
25-
if (body != null || method != HttpRequestMethod.GET){
26-
connection.doOutput = true
27-
connection.setFixedLengthStreamingMode(body?.length ?: 0)
22+
headers.union(additionalHeaders.filterNotNull()).forEach { (key, value) ->
23+
connection.setRequestProperty(key, value)
2824
}
2925

30-
body?.let { _ ->
31-
connection.outputStream.bufferedWriter().also { it.write(body) }.let { it.close() }
26+
if (body != null || method != HttpRequestMethod.GET) {
27+
connection.doOutput = true
28+
connection.setFixedLengthStreamingMode(body?.toByteArray()?.size ?: 0)
29+
connection.outputStream.bufferedWriter().use {
30+
body?.also(it::write)
31+
}
3232
}
3333

3434
connection.connect()
3535

36-
val responseCode = connection.responseCode
37-
38-
val outputBody = (connection.errorStream ?: connection.inputStream).bufferedReader().let {
39-
val text = it.readText()
40-
it.close()
41-
text
42-
}
43-
44-
val headers = connection.headerFields.keys.filterNotNull().map { HttpHeader(it, connection.getHeaderField(it)) }
45-
connection.disconnect()
46-
47-
return HttpResponse(responseCode, outputBody, headers)
36+
return HttpResponse(
37+
responseCode = connection.responseCode,
38+
body = (connection.errorStream ?: connection.inputStream).bufferedReader().use {
39+
it.readText()
40+
},
41+
headers = connection.headerFields.keys.filterNotNull().map { HttpHeader(it, connection.getHeaderField(it)) }
42+
).also { connection.disconnect() }
4843
}
4944
}
5045

51-
data class HttpResponse(val responseCode: Int, val body: String, val headers: List<HttpHeader>)
46+
data class HttpResponse(val responseCode: Int, val body: String, val headers: List<HttpHeader>)

0 commit comments

Comments
 (0)