Skip to content

Commit 87df626

Browse files
committed
Initial API implementation
1 parent de6ba9d commit 87df626

File tree

10 files changed

+365
-0
lines changed

10 files changed

+365
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package net.pearx.kpastebin
2+
3+
import net.pearx.kpastebin.model.ExpireDate
4+
import net.pearx.kpastebin.model.Privacy
5+
6+
class PastebinAccount(private val client: PastebinClient) {
7+
var userKey: String? = null
8+
9+
private fun checkUserKeyAndReturn(): String {
10+
val userKey = userKey
11+
if(userKey == null)
12+
throw InvalidUserKeyException("You haven't initialized the userKey property. It can be done by setting it directly or using the 'login' function.")
13+
else
14+
return userKey
15+
}
16+
17+
suspend fun login(username: String, password: String) {
18+
userKey = client.login(username, password)
19+
}
20+
21+
suspend fun createPaste(
22+
text: String,
23+
name: String? = null,
24+
format: String? = null,
25+
privacy: Privacy? = null,
26+
expireDate: ExpireDate? = null
27+
) = client.createPaste(text, checkUserKeyAndReturn(), name, format, privacy, expireDate)
28+
29+
suspend fun listPastes(resultsLimit: Int? = null) = client.listPastes(checkUserKeyAndReturn(), resultsLimit)
30+
31+
suspend fun deletePaste(pasteKey: String) = client.deletePaste(checkUserKeyAndReturn(), pasteKey)
32+
33+
suspend fun getUserDetails() = client.getUserDetails(checkUserKeyAndReturn())
34+
35+
suspend fun getPaste(pasteKey: String) = client.getPaste(checkUserKeyAndReturn(), pasteKey)
36+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package net.pearx.kpastebin
2+
3+
import io.ktor.client.HttpClient
4+
import io.ktor.client.features.defaultRequest
5+
import io.ktor.client.request.post
6+
import io.ktor.http.*
7+
import kotlinx.serialization.SerialName
8+
import kotlinx.serialization.Serializable
9+
import net.pearx.kpastebin.internal.*
10+
import net.pearx.kpastebin.model.ExpireDate
11+
import net.pearx.kpastebin.model.PasteDetails
12+
import net.pearx.kpastebin.model.Privacy
13+
import net.pearx.kpastebin.model.UserDetails
14+
import nl.adaptivity.xmlutil.serialization.XML
15+
import nl.adaptivity.xmlutil.serialization.XmlSerialName
16+
17+
class PastebinClient(private val devKey: String) {
18+
private val http = HttpClient {
19+
defaultRequest {
20+
contentType(ContentType.Application.FormUrlEncoded)
21+
}
22+
}
23+
24+
private val xml = XML()
25+
26+
private suspend fun sendRequest(url: String, parameters: Parameters): String {
27+
val out = http.post<String> {
28+
this.url.takeFrom(url)
29+
body = Parameters.build {
30+
append("api_dev_key", devKey)
31+
appendAll(parameters)
32+
}.formUrlEncode()
33+
}
34+
checkPastebinResponse(out)
35+
return out
36+
}
37+
38+
private suspend inline fun sendRequest(url: String, parametersBuilder: ParametersBuilder.() -> Unit) = sendRequest(url, Parameters.build(parametersBuilder))
39+
40+
suspend fun createPaste(
41+
text: String,
42+
userKey: String? = null,
43+
name: String? = null,
44+
format: String? = null,
45+
privacy: Privacy? = null,
46+
expireDate: ExpireDate? = null
47+
): String {
48+
return sendRequest(API_URL_POST) {
49+
append("api_option", "paste")
50+
append("api_paste_code", text)
51+
if (userKey != null)
52+
append("api_user_key", userKey)
53+
if(name != null)
54+
append("api_paste_name", name)
55+
if(format != null)
56+
append("api_paste_format", format)
57+
if(privacy != null)
58+
append("api_privacy", privacy.ordinal.toString())
59+
if(expireDate != null)
60+
append("api_paste_expire_date", expireDate.code)
61+
}
62+
}
63+
64+
suspend fun login(username: String, password: String): String {
65+
return sendRequest(API_URL_LOGIN) {
66+
append("api_user_name", username)
67+
append("api_user_password", password)
68+
}
69+
}
70+
71+
suspend fun listPastes(userKey: String, resultsLimit: Int? = null): List<PasteDetails> {
72+
val out = sendRequest(API_URL_POST) {
73+
append("api_user_key", userKey)
74+
append("api_option", "list")
75+
if(resultsLimit != null)
76+
append("api_results_limit", resultsLimit.toString())
77+
}
78+
if (out == "No pastes found.")
79+
return listOf()
80+
return xml.parse(ListPastesResponse.serializer(), "<root>$out</root>").data
81+
}
82+
83+
@Serializable
84+
@SerialName("root")
85+
private data class ListPastesResponse(@XmlSerialName("paste", "", "") val data: List<PasteDetails>)
86+
87+
suspend fun deletePaste(userKey: String, pasteKey: String) {
88+
val out = sendRequest(API_URL_POST) {
89+
append("api_option", "delete")
90+
append("api_user_key", userKey)
91+
append("api_paste_key", pasteKey)
92+
}
93+
if (out != "Paste Removed")
94+
throw PastebinException(out)
95+
}
96+
97+
suspend fun getUserDetails(userKey: String): UserDetails {
98+
val out = sendRequest(API_URL_POST) {
99+
append("api_option", "userdetails")
100+
append("api_user_key", userKey)
101+
}
102+
return xml.parse(UserDetails.serializer(), out)
103+
}
104+
105+
suspend fun getPaste(userKey: String, pasteKey: String): String {
106+
return sendRequest(API_URL_RAW) {
107+
append("api_option", "show_paste")
108+
append("api_user_key", userKey)
109+
append("api_paste_key", pasteKey)
110+
}
111+
}
112+
113+
//
114+
// suspend fun getPaste(pasteKey: String): String? {
115+
// return http.get<String> { url.takeFrom("$URL_RAW/$pasteKey") }
116+
// }
117+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package net.pearx.kpastebin
2+
3+
import net.pearx.kpastebin.model.Privacy
4+
5+
private const val BAD_API_REQUEST = "Bad API request, "
6+
private const val POST_LIMIT = "Post limit, "
7+
8+
internal fun checkPastebinResponse(response: String) {
9+
when {
10+
response.startsWith(BAD_API_REQUEST) -> {
11+
throw when (val sub = response.substring(BAD_API_REQUEST.length)) {
12+
"maximum number of 25 unlisted pastes for your free account" -> FreePasteLimitException(Privacy.UNLISTED, sub)
13+
"maximum number of 10 private pastes for your free account" -> FreePasteLimitException(Privacy.PRIVATE, sub)
14+
"api_paste_code was empty" -> EmptyPasteException(sub)
15+
"maximum paste file size exceeded" -> PasteSizeException(sub)
16+
"invalid api_paste_format" -> InvalidPasteFormatException(sub)
17+
"invalid api_user_key" -> InvalidUserKeyException(sub)
18+
"invalid or expired api_user_key" -> InvalidUserKeyException(sub)
19+
"invalid login" -> InvalidLoginException(sub)
20+
"account not active" -> AccountNotActiveException(sub)
21+
"invalid permission to remove paste" -> InvalidPermissionException(sub)
22+
"invalid permission to view this paste or invalid api_paste_key" -> PasteNotFoundException(sub)
23+
else -> PastebinException(sub)
24+
}
25+
}
26+
response.startsWith(POST_LIMIT) -> {
27+
throw when (val sub = response.substring(POST_LIMIT.length)) {
28+
"maximum pastes per 24h reached" -> PastePerDayLimitException(sub)
29+
else -> PastebinException(sub)
30+
}
31+
}
32+
}
33+
}
34+
35+
class InvalidUserKeyException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
36+
37+
class FreePasteLimitException(val privacy: Privacy, message: String, cause: Throwable? = null) : PastebinException(message, cause)
38+
class PasteSizeException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
39+
class EmptyPasteException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
40+
class InvalidPasteFormatException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
41+
42+
class InvalidLoginException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
43+
class AccountNotActiveException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
44+
45+
class InvalidPermissionException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
46+
47+
class PasteNotFoundException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
48+
49+
class PastePerDayLimitException(message: String, cause: Throwable? = null) : PastebinException(message, cause)
50+
51+
open class PastebinException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package net.pearx.kpastebin.internal
2+
3+
internal const val MODEL_PACKAGE = "net.pearx.kpastebin.model"
4+
5+
internal const val API_URL = "https://pastebin.com/api"
6+
internal const val API_URL_POST = "$API_URL/api_post.php"
7+
internal const val API_URL_LOGIN = "$API_URL/api_login.php"
8+
internal const val API_URL_RAW = "$API_URL/api_raw.php"
9+
internal const val URL_RAW = "https://pastebin.com/raw"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package net.pearx.kpastebin.internal
2+
3+
import kotlinx.serialization.*
4+
5+
internal open class EnumIntSerializer<T>(private val serialName: String, private val values: Array<T>) : KSerializer<T> {
6+
override val descriptor: SerialDescriptor = PrimitiveDescriptor(serialName, PrimitiveKind.INT)
7+
8+
override fun deserialize(decoder: Decoder): T {
9+
val value = decoder.decodeInt()
10+
return values[value] ?: throw NoSuchElementException("Can't find $serialName with key $value")
11+
}
12+
13+
override fun serialize(encoder: Encoder, value: T) {
14+
encoder.encodeInt(values.indexOf(value))
15+
}
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package net.pearx.kpastebin.model
2+
3+
import kotlinx.serialization.Serializable
4+
import net.pearx.kpastebin.internal.EnumIntSerializer
5+
import net.pearx.kpastebin.internal.MODEL_PACKAGE
6+
7+
@Serializable(with = AccountType.Ser::class)
8+
enum class AccountType
9+
{
10+
NORMAL,
11+
PRO;
12+
13+
internal companion object Ser : EnumIntSerializer<AccountType>("$MODEL_PACKAGE.AccountType", values())
14+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package net.pearx.kpastebin.model
2+
3+
import kotlinx.serialization.*
4+
import net.pearx.kpastebin.internal.MODEL_PACKAGE
5+
6+
@Serializable
7+
enum class ExpireDate(val code: String) {
8+
NEVER("N"),
9+
TEN_MINUTES("10M"),
10+
ONE_HOUR("1H"),
11+
ONE_DAY("1D"),
12+
ONE_WEEK("1W"),
13+
TWO_WEEEKS("2W"),
14+
ONE_MONTH("1M"),
15+
SIX_MONTHS("6M"),
16+
ONE_YEAR("1Y");
17+
18+
@Serializer(forClass = ExpireDate::class)
19+
companion object Ser : KSerializer<ExpireDate> {
20+
override val descriptor: SerialDescriptor = PrimitiveDescriptor("$MODEL_PACKAGE.ExpireDate", PrimitiveKind.STRING)
21+
22+
override fun deserialize(decoder: Decoder): ExpireDate {
23+
val value = decoder.decodeString()
24+
return values().firstOrNull { it.code == value } ?: throw NoSuchElementException("Can't find ExpireDate with key $value")
25+
}
26+
27+
override fun serialize(encoder: Encoder, value: ExpireDate) {
28+
encoder.encodeString(value.code)
29+
}
30+
}
31+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package net.pearx.kpastebin.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
import nl.adaptivity.xmlutil.serialization.XmlElement
6+
7+
@SerialName("paste")
8+
@Serializable
9+
data class PasteDetails(
10+
@SerialName("paste_key")
11+
@XmlElement(true)
12+
val key: String,
13+
@SerialName("paste_date")
14+
@XmlElement(true)
15+
val date: Int,
16+
@SerialName("paste_title")
17+
@XmlElement(true)
18+
val title: String,
19+
@SerialName("paste_size")
20+
@XmlElement(true)
21+
val size: Int,
22+
@SerialName("paste_expire_date")
23+
@XmlElement(true)
24+
val expireDate: Int,
25+
@SerialName("paste_private")
26+
@XmlElement(true)
27+
val privacy: Privacy,
28+
@SerialName("paste_format_long")
29+
@XmlElement(true)
30+
val formatLong: String,
31+
@SerialName("paste_format_short")
32+
@XmlElement(true)
33+
val formatShort: String,
34+
@SerialName("paste_url")
35+
@XmlElement(true)
36+
val url: String,
37+
@SerialName("paste_hits")
38+
@XmlElement(true)
39+
val hist: Int
40+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package net.pearx.kpastebin.model
2+
3+
import kotlinx.serialization.*
4+
import net.pearx.kpastebin.internal.EnumIntSerializer
5+
import net.pearx.kpastebin.internal.MODEL_PACKAGE
6+
7+
@Serializable(with = Privacy.Ser::class)
8+
enum class Privacy {
9+
PUBLIC,
10+
UNLISTED,
11+
PRIVATE;
12+
13+
internal companion object Ser : EnumIntSerializer<Privacy>("$MODEL_PACKAGE.Privacy", values())
14+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package net.pearx.kpastebin.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
import nl.adaptivity.xmlutil.serialization.XmlElement
6+
7+
@SerialName("user")
8+
@Serializable
9+
data class UserDetails(
10+
@SerialName("user_name")
11+
@XmlElement(true)
12+
val name: String,
13+
@SerialName("user_format_short")
14+
@XmlElement(true)
15+
val defaultFormatShort: String,
16+
@SerialName("user_expiration")
17+
@XmlElement(true)
18+
val defaultExpiration: ExpireDate,
19+
@SerialName("user_avatar_url")
20+
@XmlElement(true)
21+
val avatarUrl: String,
22+
@SerialName("user_private")
23+
@XmlElement(true)
24+
val defaultPrivacy: Privacy,
25+
@SerialName("user_website")
26+
@XmlElement(true)
27+
val website: String,
28+
@SerialName("user_email")
29+
@XmlElement(true)
30+
val email: String,
31+
@SerialName("user_location")
32+
@XmlElement(true)
33+
val location: String,
34+
@SerialName("user_account_type")
35+
@XmlElement(true)
36+
val accountType: AccountType
37+
)

0 commit comments

Comments
 (0)