diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt index 7dd5bb23f..766056a87 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt @@ -1,14 +1,20 @@ package com.vitorpamplona.amethyst.ui.actions import android.content.ContentResolver +import android.content.Context import android.net.Uri +import android.provider.OpenableColumns +import android.util.Log import android.webkit.MimeTypeMap import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.BuildConfig import okhttp3.* import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.asRequestBody import okio.BufferedSink import okio.source +import java.io.File +import java.io.FileOutputStream import java.io.IOException import java.io.InputStream @@ -21,68 +27,88 @@ object ImageUploader { fun uploadImage( uri: Uri, server: ServersAvailable, + context: Context, contentResolver: ContentResolver, onSuccess: (String, String?) -> Unit, onError: (Throwable) -> Unit ) { val contentType = contentResolver.getType(uri) val imageInputStream = contentResolver.openInputStream(uri) - checkNotNull(imageInputStream) { "Can't open the image input stream" } - val myServer = if (server == ServersAvailable.IMGUR) { ImgurServer() } else if (server == ServersAvailable.NOSTRIMG) { NostrImgServer() + } else if (server == ServersAvailable.NOSTR_BUILD) { + NostrBuildServer() } else { ImgurServer() } - uploadImage(imageInputStream, contentType, myServer, onSuccess, onError) + val file = getRealPathFromURI(uri, context)?.let { File(it) } // create path from uri + if (file != null) { + uploadImage(file, imageInputStream, contentType, myServer, server, onSuccess, onError) + } } fun uploadImage( + file: File, inputStream: InputStream, contentType: String?, server: FileServer, + serverType: ServersAvailable, 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() - - val requestBody: RequestBody = MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart( - category, - "$fileName.$extension", - object : RequestBody() { - override fun contentType(): MediaType? = - contentType?.toMediaType() - - override fun writeTo(sink: BufferedSink) { - inputStream.source().use(sink::writeAll) - } - } - ) - .build() - + val requestBody: RequestBody val requestBuilder = Request.Builder() + if (serverType == ServersAvailable.NOSTR_BUILD) { + requestBuilder.addHeader("Content-Type", "multipart/form-data") + requestBody = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart( + "fileToUpload", + "$fileName.$extension", + file.asRequestBody(contentType?.toMediaType()) + + ) + .build() + } else { + requestBody = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart( + category, + "$fileName.$extension", + + object : RequestBody() { + override fun contentType(): MediaType? = + contentType?.toMediaType() + + override fun writeTo(sink: BufferedSink) { + inputStream.source().use(sink::writeAll) + } + } + ) + .build() + } + server.clientID()?.let { - requestBuilder.header("Authorization", it) + requestBuilder.addHeader("Authorization", it) } requestBuilder - .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") + .addHeader("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .url(server.postUrl(contentType)) .post(requestBody) - val request = requestBuilder.build() client.newCall(request).enqueue(object : Callback { @@ -111,6 +137,38 @@ object ImageUploader { } } +fun getRealPathFromURI(uri: Uri, context: Context): String? { + val returnCursor = context.contentResolver.query(uri, null, null, null, null) + val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME) + returnCursor.moveToFirst() + val name = returnCursor.getString(nameIndex) + val file = File(context.filesDir, name) + try { + val inputStream: InputStream? = context.contentResolver.openInputStream(uri) + val outputStream = FileOutputStream(file) + var read = 0 + val maxBufferSize = 1 * 1024 * 1024 + val bytesAvailable: Int = inputStream?.available() ?: 0 + val bufferSize = Math.min(bytesAvailable, maxBufferSize) + val buffers = ByteArray(bufferSize) + while (inputStream?.read(buffers).also { + if (it != null) { + read = it + } + } != -1 + ) { + outputStream.write(buffers, 0, read) + } + Log.e("File Size", "Size " + file.length()) + inputStream?.close() + outputStream.close() + Log.e("File Path", "Path " + file.path) + } catch (e: java.lang.Exception) { + Log.e("Exception", e.message!!) + } + return file.path +} + abstract class FileServer { abstract fun postUrl(contentType: String?): String abstract fun parseUrlFromSucess(body: String): String? @@ -146,12 +204,10 @@ class ImgurServer : FileServer() { } class NostrBuildServer : FileServer() { - override fun postUrl(contentType: String?) = "https://nostr.build/api/upload/amethyst.php" - + override fun postUrl(contentType: String?) = "https://nostr.build/api/upload/android.php" override fun parseUrlFromSucess(body: String): String? { - val tree = jacksonObjectMapper().readTree(body) - val url = tree?.get("data")?.get("link")?.asText() - return url + val url = jacksonObjectMapper().readTree(body) // return url.toString() + return url.toString().replace("\"", "") } override fun clientID() = null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index 880192257..9827c6bb2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -44,6 +44,8 @@ open class NewMediaModel : ViewModel() { selectedServer = ServersAvailable.IMGUR_NIP_94 } else if (selectedServer == ServersAvailable.NOSTRIMG) { selectedServer = ServersAvailable.NOSTRIMG_NIP_94 + } else if (selectedServer == ServersAvailable.NOSTR_BUILD) { + selectedServer = ServersAvailable.NOSTR_BUILD_NIP_94 } } @@ -75,6 +77,7 @@ open class NewMediaModel : ViewModel() { ImageUploader.uploadImage( uri = uri, server = serverToUse, + context = context, contentResolver = contentResolver, onSuccess = { imageUrl, mimeType -> createNIP94Record(imageUrl, mimeType, description) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 477ca586d..c1489f126 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -116,6 +116,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) { val fileServers = listOf( Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_explainer)), Triple(ServersAvailable.NOSTRIMG_NIP_94, stringResource(id = R.string.upload_server_nostrimg_nip94), stringResource(id = R.string.upload_server_nostrimg_nip94_explainer)), + Triple(ServersAvailable.NOSTR_BUILD_NIP_94, stringResource(id = R.string.upload_server_nostrbuild_nip94), stringResource(id = R.string.upload_server_nostrbuild_nip94_explainer)), Triple(ServersAvailable.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer)) ) @@ -192,6 +193,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) { if (postViewModel.selectedServer == ServersAvailable.NOSTRIMG_NIP_94 || postViewModel.selectedServer == ServersAvailable.IMGUR_NIP_94 || + postViewModel.selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 || postViewModel.selectedServer == ServersAvailable.NIP95 ) { Row( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index e6fe1e9a4..370bf8728 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -590,6 +590,7 @@ enum class ServersAvailable { NOSTRIMG, IMGUR_NIP_94, NOSTRIMG_NIP_94, + NOSTR_BUILD_NIP_94, NIP95 } @@ -610,8 +611,10 @@ fun ImageVideoDescription( val fileServers = listOf( Triple(ServersAvailable.IMGUR, stringResource(id = R.string.upload_server_imgur), stringResource(id = R.string.upload_server_imgur_explainer)), Triple(ServersAvailable.NOSTRIMG, stringResource(id = R.string.upload_server_nostrimg), stringResource(id = R.string.upload_server_nostrimg_explainer)), + Triple(ServersAvailable.NOSTR_BUILD, stringResource(id = R.string.upload_server_nostrbuild), stringResource(id = R.string.upload_server_nostrbuild_explainer)), Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_explainer)), Triple(ServersAvailable.NOSTRIMG_NIP_94, stringResource(id = R.string.upload_server_nostrimg_nip94), stringResource(id = R.string.upload_server_nostrimg_nip94_explainer)), + Triple(ServersAvailable.NOSTR_BUILD_NIP_94, stringResource(id = R.string.upload_server_nostrbuild_nip94), stringResource(id = R.string.upload_server_nostrbuild_nip94_explainer)), Triple(ServersAvailable.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer)) ) @@ -741,6 +744,7 @@ fun ImageVideoDescription( if (selectedServer == ServersAvailable.NOSTRIMG_NIP_94 || selectedServer == ServersAvailable.IMGUR_NIP_94 || + selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 || selectedServer == ServersAvailable.NIP95 ) { Row( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index fd38e3031..42590becc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -145,9 +145,10 @@ open class NewPostViewModel : ViewModel() { ImageUploader.uploadImage( uri = it, server = server, + context = context, contentResolver = contentResolver, onSuccess = { imageUrl, mimeType -> - if (server == ServersAvailable.IMGUR_NIP_94 || server == ServersAvailable.NOSTRIMG_NIP_94) { + if (server == ServersAvailable.IMGUR_NIP_94 || server == ServersAvailable.NOSTRIMG_NIP_94 || server == ServersAvailable.NOSTR_BUILD_NIP_94) { createNIP94Record(imageUrl, mimeType, description) } else { isUploadingImage = false diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt index 269f22871..80abdf2cf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt @@ -171,6 +171,7 @@ class NewUserMetadataViewModel : ViewModel() { ImageUploader.uploadImage( uri = it, server = account.defaultFileServer, + context = context, contentResolver = context.contentResolver, onSuccess = { imageUrl, mimeType -> onUploading(false) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 64fcffb60..cbc905e1d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -333,12 +333,18 @@ nostrimg.com - less secure Uploads to NostrImg. NostrImg can change your image at any time + nostr.build- less secure + Uploads to nostr.build. Nostr.build can change your image at any time + Verifiable Imgur (NIP-94) Protects from Imgur modifying the content afterwards. This is a new NIP: other clients might not see the image Verifiable NostrImg (NIP-94) Protects from NostrImg modifying the content afterwards. This is a new NIP: other clients might not see the image + Verifiable Nostr.build (NIP-94) + Protects from Nostr.build modifying the content afterwards. This is a new NIP: other clients might not see the image + Your relays (NIP-95) Files are uploaded to and hosted by relays. They are free from a fixed url (third-party dependency). Make sure to have a NIP-95 relay in your relay list