Offers NostrImg as a choice of image server

This commit is contained in:
Vitor Pamplona
2023-04-26 16:36:52 -04:00
parent a669e37774
commit e370e75ba4
4 changed files with 190 additions and 20 deletions
@@ -116,7 +116,8 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
LnZapEvent.kind, LnZapRequestEvent.kind,
ChannelMessageEvent.kind, ChannelCreateEvent.kind, ChannelMetadataEvent.kind,
BadgeDefinitionEvent.kind, BadgeAwardEvent.kind, BadgeProfilesEvent.kind,
PrivateDmEvent.kind, FileHeaderEvent.kind
PrivateDmEvent.kind,
FileHeaderEvent.kind, FileStorageEvent.kind, FileStorageHeaderEvent.kind
),
ids = interestedEvents.toList()
)
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.actions
import android.content.ContentResolver
import android.net.Uri
import android.webkit.MimeTypeMap
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.BuildConfig
import okhttp3.*
@@ -9,9 +10,15 @@ import okhttp3.MediaType.Companion.toMediaType
import okio.BufferedSink
import okio.source
import java.io.IOException
import java.io.InputStream
import java.util.*
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
fun randomChars() = List(16) { charPool.random() }.joinToString("")
object ImageUploader {
fun uploadImage(
uri: Uri,
server: ServersAvailable,
@@ -20,9 +27,33 @@ object ImageUploader {
onError: (Throwable) -> Unit
) {
val contentType = contentResolver.getType(uri)
val category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image"
val imageInputStream = contentResolver.openInputStream(uri)
val url = if (category == "image") "https://api.imgur.com/3/image" else "https://api.imgur.com/3/upload"
checkNotNull(imageInputStream) {
"Can't open the image input stream"
}
val myServer = if (server == ServersAvailable.IMGUR) {
ImgurServer()
} else if (server == ServersAvailable.NOSTR_IMG) {
NostrImgServer()
} else {
ImgurServer()
}
uploadImage(imageInputStream, contentType, myServer, onSuccess, onError)
}
fun uploadImage(
inputStream: InputStream,
contentType: String?,
server: FileServer,
onSuccess: (String, String?) -> Unit,
onError: (Throwable) -> Unit
) {
val category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image"
val fileName = randomChars()
val extension = contentType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: ""
val client = OkHttpClient.Builder().build()
@@ -30,37 +61,37 @@ object ImageUploader {
.setType(MultipartBody.FORM)
.addFormDataPart(
category,
"${UUID.randomUUID()}",
"$fileName.$extension",
object : RequestBody() {
override fun contentType(): MediaType? =
contentType?.toMediaType()
override fun writeTo(sink: BufferedSink) {
val imageInputStream = contentResolver.openInputStream(uri)
checkNotNull(imageInputStream) {
"Can't open the image input stream"
}
imageInputStream.source().use(sink::writeAll)
inputStream.source().use(sink::writeAll)
}
}
)
.build()
val request: Request = Request.Builder()
.header("Authorization", "Client-ID e6aea87296f3f96")
val requestBuilder = Request.Builder()
server.clientID()?.let {
requestBuilder.header("Authorization", it)
}
requestBuilder
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.url(url)
.url(server.postUrl(contentType))
.post(requestBody)
.build()
val request = requestBuilder.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
try {
check(response.isSuccessful)
response.body.use { body ->
val tree = jacksonObjectMapper().readTree(body.string())
val url = tree?.get("data")?.get("link")?.asText()
val url = server.parseUrlFromSucess(body.string())
checkNotNull(url) {
"There must be an uploaded image URL in the response"
}
@@ -80,3 +111,49 @@ object ImageUploader {
})
}
}
abstract class FileServer {
abstract fun postUrl(contentType: String?): String
abstract fun parseUrlFromSucess(body: String): String?
open fun clientID(): String? = null
}
class NostrImgServer : FileServer() {
override fun postUrl(contentType: String?) = "https://nostrimg.com/api/upload"
override fun parseUrlFromSucess(body: String): String? {
val tree = jacksonObjectMapper().readTree(body)
val url = tree?.get("data")?.get("link")?.asText()
return url
}
override fun clientID() = null
}
class ImgurServer : FileServer() {
override fun postUrl(contentType: String?): String {
val category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image"
return if (category == "image") "https://api.imgur.com/3/image" else "https://api.imgur.com/3/upload"
}
override fun parseUrlFromSucess(body: String): String? {
val tree = jacksonObjectMapper().readTree(body)
val url = tree?.get("data")?.get("link")?.asText()
return url
}
override fun clientID() = "Client-ID e6aea87296f3f96"
}
class NostrBuildServer : FileServer() {
override fun postUrl(contentType: String?) = "https://nostr.build/api/upload/amethyst.php"
override fun parseUrlFromSucess(body: String): String? {
val tree = jacksonObjectMapper().readTree(body)
val url = tree?.get("data")?.get("link")?.asText()
return url
}
override fun clientID() = null
}
@@ -488,8 +488,8 @@ fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier
}
}
enum class ServersAvailable() {
IMGUR
enum class ServersAvailable {
IMGUR, NOSTR_BUILD, NOSTR_IMG
}
@Composable
@@ -507,7 +507,8 @@ fun ImageVideoDescription(
val isVideo = mediaType.startsWith("video")
val fileServers = listOf(
Pair(ServersAvailable.IMGUR, "imgur.com")
Pair(ServersAvailable.IMGUR, "imgur.com"),
Pair(ServersAvailable.NOSTR_IMG, "nostrimg.com")
)
val fileServerOptions = fileServers.map { it.second }
@@ -618,7 +619,7 @@ fun ImageVideoDescription(
) {
TextSpinner(
label = stringResource(id = R.string.file_server),
placeholder = fileServers.filter { it.first == defaultServer }.first().second,
placeholder = fileServers.filter { it.first == defaultServer }.firstOrNull()?.second ?: fileServers[0].second,
options = fileServerOptions,
onSelect = {
selectedServer = fileServers[it].first