Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ subprojects {
"implementation"("io.ktor:ktor-client-websockets:$ktor_version")
"implementation"("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
"implementation"("io.github.aakira:napier:2.6.1")
"implementation"("com.michael-bull.kotlin-result:kotlin-result:1.1.16")

"testImplementation"("org.junit.jupiter:junit-jupiter-api:5.9.0")
"testImplementation"("org.junit.jupiter:junit-jupiter-params:5.9.0")
Expand Down
9 changes: 6 additions & 3 deletions core/src/main/kotlin/dev/usbharu/multim/MultiM.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package dev.usbharu.multim

import com.github.michaelbull.result.Result
import com.github.michaelbull.result.flatMap
import com.github.michaelbull.result.map
import dev.usbharu.multim.api.NodeinfoApi
import dev.usbharu.multim.error.MultiMError
import dev.usbharu.multim.factory.MultiMApis
import dev.usbharu.multim.factory.PlatformApiFactory
import dev.usbharu.multim.multi.MultiAccountApiBase
Expand Down Expand Up @@ -30,10 +34,9 @@ object MultiM {
token: String,
factory: PlatformApiFactory,
httpClient: HttpClient = httpClientWithJson
): MultiMApis {
): Result<MultiMApis,MultiMError> {

val nodeinfo = NodeinfoApi(httpClient).nodeinfo(url)
return factory.factory(nodeInfo = nodeinfo, httpClient, token, url)
return NodeinfoApi(httpClient).nodeinfo(url).map{ nodeInfo -> factory.factory(nodeInfo,httpClient,token,url) }
}

fun createMultiAccountClients(serviceInfoList: List<ServiceInfo> = listOf()): MultiAccountApiBase {
Expand Down
20 changes: 11 additions & 9 deletions core/src/main/kotlin/dev/usbharu/multim/api/AccountApi.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.usbharu.multim.api

import com.github.michaelbull.result.Result
import dev.usbharu.multim.error.MultiMError
import dev.usbharu.multim.model.*

//todo 成功したかをboolで返しているが、詳細がわからないのでしっかり返す。
Expand All @@ -9,14 +11,14 @@ interface AccountApi {
account: Account,
since: StatusId? = null,
until: StatusId? = null
): List<Status>
): Result<List<Status>,MultiMError>

suspend fun follow(account: Account): Boolean
suspend fun unfollow(account: Account): Boolean
suspend fun profile(account: Account): Profile
suspend fun statuses(account: Account, includeRepost: Boolean = false): List<Status>
suspend fun relationships(myself: Account, other: Account): Relation
suspend fun requestCancel(account: Account): Boolean
suspend fun requestAccept(account: Account): Boolean
suspend fun requestReject(account: Account): Boolean
suspend fun follow(account: Account): Result<Unit,MultiMError>
suspend fun unfollow(account: Account): Result<Unit,MultiMError>
suspend fun profile(account: Account): Result<Profile,MultiMError>
suspend fun statuses(account: Account, includeRepost: Boolean = false): Result<List<Status>,MultiMError>
suspend fun relationships(myself: Account, other: Account): Result<Relation,MultiMError>
suspend fun requestCancel(account: Account): Result<Unit,MultiMError>
suspend fun requestAccept(account: Account): Result<Unit,MultiMError>
suspend fun requestReject(account: Account): Result<Unit,MultiMError>
}
70 changes: 51 additions & 19 deletions core/src/main/kotlin/dev/usbharu/multim/api/ApiClient.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package dev.usbharu.multim.api

import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import dev.usbharu.multim.MultiM
import dev.usbharu.multim.error.*
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
Expand All @@ -14,38 +18,66 @@ abstract class ApiClient(var baseUrl: String, val client: HttpClient) {
return createHttpClient(config)
}

suspend inline fun <reified R> post(path: String, baseUrl: String = this.baseUrl): R {
val post = client.post(baseUrl + path)
return post.body()
suspend inline fun <reified R> postEmpty(
path: String,
baseUrl: String = this.baseUrl
): Result<R, ThrowableError> {
val post = try {
client.post(baseUrl + path)
} catch (e: ClientRequestException) {
return Err(HttpClientClientError(e))
} catch (e: ServerResponseException) {
return Err(HttpClientServerError(e))
}
return runCatching<R> { post.body() }.fold(
onSuccess = { Ok(it) },
onFailure = { Err(ThrowableError(it)) })
}

suspend inline fun <reified T, reified R> post(
content: T,
path: String,
baseUrl: String = this.baseUrl
): R {
val post = client.post(baseUrl + path) {
contentType(ContentType.Application.Json)
setBody(content)
}

return post.body()
): Result<R, ThrowableError> {
val post =
try {
client.post(baseUrl + path) {
contentType(ContentType.Application.Json)
setBody(content)
}
} catch (e: ClientRequestException) {
return Err(HttpClientClientError(e))
} catch (e: ServerResponseException) {
return Err(HttpClientServerError(e))
}
return runCatching<R> { post.body() }.fold(
onSuccess = { Ok(it) },
onFailure = { Err(ThrowableError(it)) })
}

suspend inline fun <reified T> postWithoutResponse(
content: T,
path: String,
baseUrl: String = this.baseUrl
) {
client.post(baseUrl + path) {
contentType(ContentType.Application.Json)
setBody(content)
}

): Result<Unit, ThrowableError> {
return runCatching<Unit> {
client.post(baseUrl + path) {
contentType(ContentType.Application.Json)
setBody(content)
}
}.fold(onSuccess = Ok(), onFailure = ThrowableError())
}

suspend fun get(path: String, block: HttpRequestBuilder.() -> Unit): HttpResponse {
return client.get(baseUrl + path, block)
suspend fun get(
path: String,
block: HttpRequestBuilder.() -> Unit
): Result<HttpResponse, ThrowableError> {
return runCatching<HttpResponse> {
client.get(
baseUrl + path,
block
)
}.fold(onSuccess = { Ok(it) }, onFailure = { Err(ThrowableError(it)) })
}
}

Expand Down
58 changes: 50 additions & 8 deletions core/src/main/kotlin/dev/usbharu/multim/api/NodeinfoApi.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package dev.usbharu.multim.api

import com.github.michaelbull.result.*
import dev.usbharu.multim.MultiM.json
import dev.usbharu.multim.error.*
import dev.usbharu.multim.model.wellknown.NodeinfoList
import dev.usbharu.multim.model.wellknown.nodeinfo.NodeInfo
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlin.runCatching

// todo well-knwonだけでパッケージ作ったほうがいいかも
class NodeinfoApi(private var httpClient: HttpClient) {
Expand All @@ -20,22 +24,60 @@ class NodeinfoApi(private var httpClient: HttpClient) {
}
}

suspend fun wellKnownNodeinfo(url: String): NodeinfoList {
return httpClient.get("$url.well-known/nodeinfo").body()
suspend fun wellKnownNodeinfo(url: String): Result<NodeinfoList, MultiMError> {
val get = try {
httpClient.get("$url.well-known/nodeinfo")
} catch (e: ServerResponseException) {
return Err(
MultiMHttpError(
HttpError.ServerError(e.response.status.value, e.message, e), e
)
)
} catch (e: ClientRequestException) {
return Err(
MultiMHttpError(
HttpError.ClientError(e.response.status.value, e.message, e), e
)
)
}
return runCatching<NodeinfoList> { get.body() }.foldWithOk {
MultiMJsonContentTransformError(
it.localizedMessage
)
}
}

fun nodeinfoLink(nodeinfoList: NodeinfoList): NodeinfoList.NodeinfoLink {
return nodeinfoList.links.minByOrNull { it.rel.substringAfterLast("/", "0").toFloat() }!!
fun nodeinfoLink(nodeinfoList: NodeinfoList): Result<NodeinfoList.NodeinfoLink, MultiMError> {
return runCatching<NodeinfoList.NodeinfoLink> {
nodeinfoList.links.minByOrNull { it.rel.substringAfterLast("/", "0").toFloat() }!!
}.foldWithOk {
MultiMError(it.localizedMessage, it, ErrorType.API)
}
}


// todo 強制で2.0のが返ってくるのでバージョンを識別する
suspend fun nodeinfo(nodeinfoLink: NodeinfoList.NodeinfoLink): NodeInfo {
suspend fun nodeinfo(nodeinfoLink: NodeinfoList.NodeinfoLink): Result<NodeInfo, MultiMError> {

return httpClient.get(nodeinfoLink.href).body()
val get = try {
httpClient.get(nodeinfoLink.href)
} catch (e: ServerResponseException) {
return Err(
MultiMHttpError(e)
)
} catch (e: ClientRequestException) {
return Err(MultiMHttpError(e))
}
return runCatching<NodeInfo> { get.body() }.foldWithOk {
MultiMJsonContentTransformError(
it.localizedMessage
)
}
}

suspend fun nodeinfo(url: String): NodeInfo {
return nodeinfo(nodeinfoLink(wellKnownNodeinfo(url)))
suspend fun nodeinfo(url: String): Result<NodeInfo, MultiMError> {
return wellKnownNodeinfo(url)
.flatMap { nodeinfoLink(it) }
.flatMap { nodeinfo(it) }
}
}
28 changes: 15 additions & 13 deletions core/src/main/kotlin/dev/usbharu/multim/api/StatusApi.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package dev.usbharu.multim.api

import com.github.michaelbull.result.Result
import dev.usbharu.multim.error.MultiMError
import dev.usbharu.multim.model.*

interface StatusApi {
suspend fun post(status: StatusForPost): Status
suspend fun delete(id: StatusId): Boolean
suspend fun findById(id: StatusId): Status
suspend fun addReaction(id: StatusId, reaction: Reaction): Boolean
suspend fun post(status: StatusForPost): Result<Status,MultiMError>
suspend fun delete(id: StatusId): Result<Unit,MultiMError>
suspend fun findById(id: StatusId): Result<Status,MultiMError>
suspend fun addReaction(id: StatusId, reaction: Reaction): Result<Unit,MultiMError>


/**
Expand All @@ -16,13 +18,13 @@ interface StatusApi {
* @param reaction 実装によって挙動が変わります。nullでも
* @return
*/
suspend fun removeReaction(id: StatusId, reaction: Reaction?): Boolean
suspend fun reactions(id: StatusId): Map<Reaction, Int>
suspend fun replies(id: StatusId): List<Status>
suspend fun repost(id: StatusId): Status
suspend fun unRepost(id: StatusId): Boolean
suspend fun replyTo(id: StatusId, status: StatusForPost): Status
suspend fun addToBookmarks(id: StatusId): Boolean
suspend fun removeFromBookmarks(id: StatusId): Boolean
suspend fun getPreviousAndNext(id: StatusId): PreviousAndNextPosts
suspend fun removeReaction(id: StatusId, reaction: Reaction?): Result<Unit,MultiMError>
suspend fun reactions(id: StatusId): Result<Map<Reaction, Int>,MultiMError>
suspend fun replies(id: StatusId): Result<List<Status>,MultiMError>
suspend fun repost(id: StatusId): Result<Status,MultiMError>
suspend fun unRepost(id: StatusId): Result<Unit,MultiMError>
suspend fun replyTo(id: StatusId, status: StatusForPost): Result<Status,MultiMError>
suspend fun addToBookmarks(id: StatusId): Result<Unit,MultiMError>
suspend fun removeFromBookmarks(id: StatusId): Result<Unit,MultiMError>
suspend fun getPreviousAndNext(id: StatusId): Result<PreviousAndNextPosts,MultiMError>
}
10 changes: 6 additions & 4 deletions core/src/main/kotlin/dev/usbharu/multim/api/TimelineApi.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package dev.usbharu.multim.api

import com.github.michaelbull.result.Result
import dev.usbharu.multim.error.MultiMError
import dev.usbharu.multim.model.Status
import dev.usbharu.multim.model.Timeline

interface TimelineApi {
suspend fun availableTimelines(): List<Timeline>
suspend fun availableTimelines(): Result<List<Timeline>,MultiMError>

suspend fun listen(timeline: Timeline,callback: (List<Status>) -> Unit)
suspend fun listen(timeline: Timeline,callback: (List<Status>) -> Unit):Result<Unit,MultiMError>

// todo 返り値を詳細にする
suspend fun connect(timeline: Timeline): Boolean
suspend fun connect(timeline: Timeline): Result<Unit,MultiMError>

/**
* Disconnect
Expand All @@ -18,7 +20,7 @@ interface TimelineApi {
* @param force 強制的に切断し、もし受信しても無視するように要求する。
* @return
*/
suspend fun disconnect(timeline: Timeline, force: Boolean = false): Boolean
suspend fun disconnect(timeline: Timeline, force: Boolean = false): Result<Unit,MultiMError>


}
33 changes: 33 additions & 0 deletions core/src/main/kotlin/dev/usbharu/multim/error/Error.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.usbharu.multim.error

import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result as MichaelbullResultResult

abstract class Error(open val message: String)

open class ThrowableError(
val throwable: Throwable,
override val message: String = throwable.localizedMessage
) : Error(message)

class SimpleError(message: String) : Error(message)

fun <R> Ok(): (R) -> Ok<R> {
return { Ok(it) }
}

fun ThrowableError(): (Throwable) -> Err<ThrowableError> {
return { Err(ThrowableError(it)) }
}

fun <T,R : Error> Error(error: (T) -> R): (T) -> Err<R> {
return { Err(error(it)) }
}

fun <A,B : Error> Result<A>.foldWithOk(onSuccess:(A)->Ok<A> = Ok(), onFailure:(Throwable)->B): MichaelbullResultResult<A,B> {
return fold(
onSuccess = onSuccess,
onFailure = Error(onFailure)
)
}
12 changes: 12 additions & 0 deletions core/src/main/kotlin/dev/usbharu/multim/error/HttpClientError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dev.usbharu.multim.error

import io.ktor.client.plugins.*

abstract class HttpClientError(throwable: ResponseException, message: String = throwable.localizedMessage) :
ThrowableError(throwable, message)

class HttpClientServerError(throwable: ServerResponseException, message: String = throwable.localizedMessage) :
HttpClientError(throwable, message)

class HttpClientClientError(throwable: ClientRequestException, message: String = throwable.localizedMessage) :
HttpClientError(throwable, message)
Loading