diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt index 0231a15..2292726 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt @@ -41,7 +41,35 @@ import java.util.logging.Level * * See the [Bitly API](https://dev.bitly.com/v4/#tag/Bitlinks) for more information. */ -class Bitlinks(private val accessToken: String) { +open class Bitlinks(val accessToken: String) { + inner class Clicks { + fun summary( + bitlink: String, + unit: Units = Units.DAY, + units: Int = -1, + size: Int = 50, + unit_reference: String = Constants.EMPTY, + isJson: Boolean = false + ): String { + var clicks = if (isJson) "{}" else Constants.EMPTY + if (bitlink.isNotBlank()) { + val response = Utils.call( + accessToken, + Utils.buildEndPointUrl("/bitlinks/" + bitlink.removeHttp() + "/clicks/summary"), + hashMapOf( + Pair("unit", unit.toString().toLowerCase()), + Pair("units", units.toString()), + Pair("size", size.toString()), + Pair("unit_reference", unit_reference) + ), + Methods.GET + ) + clicks = parseJsonResponse(response, "total_clicks", clicks, isJson) + } + return clicks + } + } + /** * Expands a Bitlink. * @@ -58,7 +86,7 @@ class Bitlinks(private val accessToken: String) { val response = Utils.call( accessToken, Utils.buildEndPointUrl("/expand"), - mapOf(Pair("bitlink_id", bitlink_id.removePrefix("http://").removePrefix("https://"))), + mapOf(Pair("bitlink_id", bitlink_id.removeHttp())), Methods.POST ) longUrl = parseJsonResponse(response, "long_url", longUrl, isJson) @@ -69,11 +97,31 @@ class Bitlinks(private val accessToken: String) { private fun JSONObject.getString(key: String, default: String): String { return if (this.has(key)) - this.getString(key) + this.get(key).toString() else default } + private fun parseJsonResponse(response: String, key: String, default: String, isJson: Boolean): String { + var parsed = default + if (response.isNotEmpty()) { + if (isJson) { + parsed = response + } else { + try { + parsed = JSONObject(response).getString(key, default) + } catch (jse: JSONException) { + Utils.logger.log(Level.SEVERE, "An error occurred parsing the response from Bitly.", jse) + } + } + } + return parsed + } + + private fun String.removeHttp(): String { + return this.replaceFirst(Regex("^[Hh][Tt]{2}[Pp][Ss]?://"), "") + } + /** * Shortens a long URL. * @@ -112,20 +160,4 @@ class Bitlinks(private val accessToken: String) { return bitlink } - - private fun parseJsonResponse(response: String, key: String, default: String, isJson: Boolean): String { - var parsed = default - if (response.isNotEmpty()) { - if (isJson) { - parsed = response - } else { - try { - parsed = JSONObject(response).getString(key, default) - } catch (jse: JSONException) { - Utils.logger.log(Level.SEVERE, "An error occurred parsing the response from Bitly.", jse) - } - } - } - return parsed - } } diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Constants.kt b/src/main/kotlin/net/thauvin/erik/bitly/Constants.kt index 0ee97ae..c02065d 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/Constants.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/Constants.kt @@ -33,7 +33,7 @@ package net.thauvin.erik.bitly /** Constants for this package. **/ -class Constants private constructor() { +open class Constants private constructor() { companion object Constants { /** The Bitly API base URL. **/ const val API_BASE_URL = "https://api-ssl.bitly.com/v4" diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Units.kt b/src/main/kotlin/net/thauvin/erik/bitly/Units.kt new file mode 100644 index 0000000..f1fa2a1 --- /dev/null +++ b/src/main/kotlin/net/thauvin/erik/bitly/Units.kt @@ -0,0 +1,43 @@ +/* + * Units.kt + * + * Copyright (c) 2020, Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.thauvin.erik.bitly + +/** Units of time **/ +@Suppress("unused") +enum class Units { + MINUTE, + HOUR, + DAY, + WEEK, + MONTH +} diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt b/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt index 02c3ee2..eb2f26b 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt @@ -36,6 +36,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request +import okhttp3.RequestBody.Companion.create import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import okhttp3.logging.HttpLoggingInterceptor @@ -47,21 +48,11 @@ import java.util.logging.Level import java.util.logging.Logger /** Useful functions. */ -class Utils private constructor() { +open class Utils private constructor() { companion object { /** The logger instance. **/ val logger: Logger by lazy { Logger.getLogger(Bitly::class.java.simpleName) } - private val client: OkHttpClient = if (logger.isLoggable(Level.FINE)) { - val httpLoggingInterceptor = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - redactHeader("Authorization") - } - OkHttpClient.Builder().addInterceptor(httpLoggingInterceptor).build() - } else { - OkHttpClient.Builder().build() - } - /** * Builds the full API endpoint URL using the [Constants.API_BASE_URL]. * @@ -118,13 +109,25 @@ class Utils private constructor() { } }.addHeader("Authorization", "Bearer $accessToken") - val result = client.newCall(builder.build()).execute() + val result = createHttpClient().newCall(builder.build()).execute() response = parseBody(endPoint, result) } } return response } + private fun createHttpClient() : OkHttpClient { + return if (logger.isLoggable(Level.FINE)) { + val httpLoggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + redactHeader("Authorization") + } + OkHttpClient.Builder().addInterceptor(httpLoggingInterceptor).build() + } else { + OkHttpClient.Builder().build() + } + } + private fun parseBody(endPoint: String, result: Response): String { val body = result.body?.string() if (body != null) { diff --git a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt index eb3b7ac..9f75f5d 100644 --- a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt +++ b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt @@ -37,6 +37,7 @@ import java.io.File import java.util.logging.Level import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotEquals import kotlin.test.assertTrue class BitlyTest { @@ -69,7 +70,10 @@ class BitlyTest { @Test fun `token should be valid`() { val test = Bitly().apply { accessToken = "12345679" } - assertEquals("{\"message\":\"FORBIDDEN\"}", test.bitlinks().shorten("https://erik.thauvin.net/blog", isJson = true)) + assertEquals( + "{\"message\":\"FORBIDDEN\"}", + test.bitlinks().shorten("https://erik.thauvin.net/blog", isJson = true) + ) } @Test @@ -95,11 +99,16 @@ class BitlyTest { @Test fun `bitlinks shorten`() { - assertEquals(shortUrl, Bitlinks(bitly.accessToken).shorten(longUrl, domain="bit.ly")) + assertEquals(shortUrl, Bitlinks(bitly.accessToken).shorten(longUrl, domain = "bit.ly")) } @Test fun `bitlinks expand`() { assertEquals(longUrl, Bitlinks(bitly.accessToken).expand(shortUrl)) } + + @Test + fun `clicks summary`() { + assertNotEquals(Constants.EMPTY, bitly.bitlinks().Clicks().summary(shortUrl)) + } }