diff --git a/gradle.properties b/gradle.properties index 736f0f8..55b7db7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ kotlin.code.style=official org.gradle.caching=true -version = 0.0.2-SNAPSHOT \ No newline at end of file +version = 0.0.4-SNAPSHOT diff --git a/src/main/kotlin/dev/roava/api/ThumbnailApi.kt b/src/main/kotlin/dev/roava/api/ThumbnailApi.kt new file mode 100644 index 0000000..08334c8 --- /dev/null +++ b/src/main/kotlin/dev/roava/api/ThumbnailApi.kt @@ -0,0 +1,54 @@ +/* + * MIT License + * + * Copyright (c) 2024 RoavaDev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.roava.api + +import dev.roava.json.user.ThumbnailListData +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Query + +interface ThumbnailApi { + @GET("/v1/users/avatar") + fun getAvatar( + @Query("userIds") userId: Long, + @Query("size") size: String, + @Query("format") format: String, + @Query("isCircular") circular: Boolean + ): Call + @GET("/v1/users/avatar-headshot") + fun getHeadShot( + @Query("userIds") userId: Long, + @Query("size") size: String, + @Query("format") format: String, + @Query("isCircular") circular: Boolean + ): Call + @GET("/v1/users/avatar-bust") + fun getBust( + @Query("userIds") userId: Long, + @Query("size") size: String, + @Query("format") format: String, + @Query("isCircular") circular: Boolean + ): Call +} diff --git a/src/main/kotlin/dev/roava/client/RoavaClient.kt b/src/main/kotlin/dev/roava/client/RoavaClient.kt index 1457f7a..ab9e2ed 100644 --- a/src/main/kotlin/dev/roava/client/RoavaClient.kt +++ b/src/main/kotlin/dev/roava/client/RoavaClient.kt @@ -58,7 +58,7 @@ class RoavaClient { throw RuntimeException("Your cookie is not set properly! Please make sure that you include the entirety of the string, including the _|WARNING:-") } - request = dev.roava.client.RoavaRequest(cookie) + request = RoavaRequest(cookie) this.cookie = cookie try { diff --git a/src/main/kotlin/dev/roava/client/RoavaInterceptor.kt b/src/main/kotlin/dev/roava/client/RoavaInterceptor.kt index 7b4aaf5..7570a46 100644 --- a/src/main/kotlin/dev/roava/client/RoavaInterceptor.kt +++ b/src/main/kotlin/dev/roava/client/RoavaInterceptor.kt @@ -32,11 +32,20 @@ import okhttp3.Response * For Intercepting calls and adding the X-CSRF-TOKEN to the header if the original request failed (for internal use only). */ internal class RoavaInterceptor: Interceptor { + private val header = "X-CSRF-TOKEN" + + private var token: String? = null + override fun intercept(chain: Interceptor.Chain): Response { - var response = chain.proceed(chain.request()) + val orig = chain.request() + val request = orig.newBuilder() + .header(header, token ?: "") + .build() + + var response = chain.proceed(request) if (response.code() == 403 && !hasToken(response)) { - val token: String? = response.header("X-CSRF-TOKEN") + token = response.header(header) token?.let { response.close() @@ -50,7 +59,7 @@ internal class RoavaInterceptor: Interceptor { private fun hasToken(response: Response?): Boolean { response?.let { - return !it.request().header("X-CSRF-TOKEN").isNullOrEmpty() + return !it.request().header(header).isNullOrEmpty() } return false @@ -58,7 +67,7 @@ internal class RoavaInterceptor: Interceptor { private fun retry(request: Request?, token: String): Request? { return request?.newBuilder() - ?.header("X-CSRF-TOKEN", token) + ?.header(header, token) ?.build() } } \ No newline at end of file diff --git a/src/main/kotlin/dev/roava/group/Group.kt b/src/main/kotlin/dev/roava/group/Group.kt index caf28ec..1263cf8 100644 --- a/src/main/kotlin/dev/roava/group/Group.kt +++ b/src/main/kotlin/dev/roava/group/Group.kt @@ -30,6 +30,7 @@ import dev.roava.client.RoavaRequest import dev.roava.json.group.GroupData import dev.roava.json.group.RoleRequest import dev.roava.user.User +import retrofit2.HttpException /** * A class which represents a Group. @@ -234,16 +235,21 @@ class Group { roleNumber = getRole(roleNumber).id } - runCatching { + val result = runCatching { client.request.createRequest(GroupApi::class.java, "groups") .rankUser(id, userId, RoleRequest(roleNumber)) - .execute().isSuccessful.also { - if (!it) { - throw RuntimeException("Could not rank the provided user!") - } - } - }.onFailure { - throw RuntimeException("Could not rank the provided user!") + .execute() + } + + result.onFailure { exception -> + if (exception is HttpException) { + val errorCode = exception.code() + val message = exception.message() + + throw RuntimeException("Ranking user with id $userId failed with message \"$message\" and response code $errorCode") + } else { + throw RuntimeException("An unknown error has occurred while ranking the user!") + } } } diff --git a/src/main/kotlin/dev/roava/json/user/ThumbnailData.kt b/src/main/kotlin/dev/roava/json/user/ThumbnailData.kt new file mode 100644 index 0000000..14ee7f4 --- /dev/null +++ b/src/main/kotlin/dev/roava/json/user/ThumbnailData.kt @@ -0,0 +1,33 @@ +/* + * MIT License + * + * Copyright (c) 2024 RoavaDev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.roava.json.user + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +@JsonIgnoreProperties(ignoreUnknown = true) +data class ThumbnailData( + @JsonProperty("imageUrl") + val thumbnail: String? +) \ No newline at end of file diff --git a/src/main/kotlin/dev/roava/json/user/ThumbnailListData.kt b/src/main/kotlin/dev/roava/json/user/ThumbnailListData.kt new file mode 100644 index 0000000..3f96014 --- /dev/null +++ b/src/main/kotlin/dev/roava/json/user/ThumbnailListData.kt @@ -0,0 +1,33 @@ +/* + * MIT License + * + * Copyright (c) 2024 RoavaDev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.roava.json.user + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +@JsonIgnoreProperties(ignoreUnknown = true) +data class ThumbnailListData ( + @JsonProperty("data") + val data: List? +) \ No newline at end of file diff --git a/src/main/kotlin/dev/roava/user/User.kt b/src/main/kotlin/dev/roava/user/User.kt index abe198f..63b6172 100644 --- a/src/main/kotlin/dev/roava/user/User.kt +++ b/src/main/kotlin/dev/roava/user/User.kt @@ -24,14 +24,12 @@ package dev.roava.user -import dev.roava.api.FriendApi -import dev.roava.api.GroupApi -import dev.roava.api.InventoryApi -import dev.roava.api.UserApi +import dev.roava.api.* import dev.roava.client.RoavaRequest import dev.roava.group.Group import dev.roava.json.user.UserData import dev.roava.json.user.UserNameRequest +import retrofit2.HttpException /** * A class which represents a User which is not authenticated by the [dev.roava.client.RoavaClient]. @@ -154,22 +152,123 @@ class User { fun getGroups(): List { val groups = mutableListOf() - try { - val groupData = request.createRequest(GroupApi::class.java, "groups") + val result = runCatching { + request.createRequest(GroupApi::class.java, "groups") .getUserRoleInfo(id) .execute() - .body() + } + result.onFailure { exception -> + if (exception is HttpException) { + val errorCode = exception.code() + val message = exception.message() + + throw RuntimeException("Ranking user with id ${this.id} failed with message \"$message\" and response code $errorCode") + } else { + throw RuntimeException("An unknown error has occurred while fetching the user's groups!") + } + }.onSuccess { + val groupData = it.body() + for (group in groupData?.data!!) { groups.add(Group(group.groupData!!)) } - } catch(exception: Exception) { - throw RuntimeException("Could not fetch the user's groups!") } return groups.toList() } + /** + * Method to get a User's Avatar + * + * Available Values: + * 30x30, 48x48, 60x60, 75x75, 100x100, 110x110, 140x140, 150x150, 150x200, 180x180, 250x250, 352x352, 420x420, 720x720 + * @throws[RuntimeException] + * @return[String] + */ + @Throws(RuntimeException::class) + fun getAvatar(size: String,isCircular: Boolean): String { + var thumbnail = "" + val result = runCatching { + request.createRequest(ThumbnailApi::class.java, "thumbnails") + .getAvatar(id,size,"Png",isCircular) + .execute() + } + result.onFailure { exception -> + if (exception is HttpException) { + val errorCode = exception.code() + val message = exception.message() + + throw RuntimeException("Grabbing thumbnail of user with id ${this.id} failed with message \"$message\" and response code $errorCode") + } else { + throw RuntimeException("an unknown error has occurred while fetching the user's thumbnail!\n${exception.message}") + } + }.onSuccess { + thumbnail = it.body()?.data?.get(0)?.thumbnail?: "" + } + return thumbnail + } + + /** + * Method to get a User's Headshot + * + * Available Values: + * 30x30, 48x48, 60x60, 75x75, 100x100, 110x110, 140x140, 150x150, 150x200, 180x180, 250x250, 352x352, 420x420, 720x720 + * @throws[RuntimeException] + * @return[String] + */ + @Throws(RuntimeException::class) + fun getHeadShot(size: String,isCircular: Boolean): String { + var thumbnail = "" + val result = runCatching { + request.createRequest(ThumbnailApi::class.java, "thumbnails") + .getHeadShot(id, size, "Png", isCircular) + .execute() + } + result.onFailure { exception -> + if (exception is HttpException) { + val errorCode = exception.code() + val message = exception.message() + + throw RuntimeException("Grabbing headshot of user with id ${this.id} failed with message \"$message\" and response code $errorCode") + } else { + throw RuntimeException("an unknown error has occurred while fetching the user's headshot!\n${exception.message}") + } + }.onSuccess { + thumbnail = it.body()?.data?.get(0)?.thumbnail ?: "" + } + return thumbnail + } + /** + * Method to get a User's Bust + * + * Available Values: + * 48x48, 50x50, 60x60, 75x75, 100x100, 150x150, 180x180, 352x352, 420x420 + * @throws[RuntimeException] + * @return[String] + */ + @Throws(RuntimeException::class) + fun getBust(size: String,isCircular: Boolean): String { + var thumbnail = "" + val result = runCatching { + request.createRequest(ThumbnailApi::class.java, "thumbnails") + .getBust(id, size, "Png", isCircular) + .execute() + } + result.onFailure { exception -> + if (exception is HttpException) { + val errorCode = exception.code() + val message = exception.message() + + throw RuntimeException("Grabbing bust of user with id ${this.id} failed with message \"$message\" and response code $errorCode") + } else { + throw RuntimeException("an unknown error has occurred while fetching the user's bust!\n${exception.message}") + } + }.onSuccess { + thumbnail = it.body()?.data?.get(0)?.thumbnail ?: "" + } + return thumbnail + } /** * Method to get if a User is in a group * diff --git a/src/test/kotlin/dev/roava/user/UserTest.kt b/src/test/kotlin/dev/roava/user/UserTest.kt index 90c6071..17ada75 100644 --- a/src/test/kotlin/dev/roava/user/UserTest.kt +++ b/src/test/kotlin/dev/roava/user/UserTest.kt @@ -27,6 +27,7 @@ package dev.roava.user import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.* +import kotlin.test.assertContains internal class UserTest { private val testUser = User(3838771115) @@ -91,4 +92,16 @@ internal class UserTest { fun testDescription() { assertEquals(testUser.description, "This is a test description") } + @Test + fun testAvatar() { + assertContains(testUser.getAvatar("30x30",true), "https://tr.rbxcdn.com/") + } + @Test + fun testHeadShot(){ + assertContains(testUser.getHeadShot("48x48", true), "https://tr.rbxcdn.com/") + } + @Test + fun testBust(){ + assertContains(testUser.getBust("48x48",true), "https://tr.rbxcdn.com/") + } } \ No newline at end of file