From f889ff25473f19b3a98e9583877194784af7b1f7 Mon Sep 17 00:00:00 2001 From: Believethehype Date: Tue, 2 May 2023 00:23:59 +0200 Subject: [PATCH 1/3] added nostrbuild, can only upload very small files right now --- .../amethyst/ui/actions/ImageUploader.kt | 28 +++++++++++-------- .../amethyst/ui/actions/NewPostView.kt | 1 + app/src/main/res/values/strings.xml | 3 ++ 3 files changed, 21 insertions(+), 11 deletions(-) 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..be6095797 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 @@ -31,26 +31,31 @@ object ImageUploader { 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) + uploadImage(imageInputStream, contentType, myServer, server, onSuccess, onError) } fun uploadImage( inputStream: InputStream, contentType: String?, server: FileServer, + serverType: ServersAvailable, onSuccess: (String, String?) -> Unit, onError: (Throwable) -> Unit ) { - val category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image" + var category = "fileToUpload" + if (serverType != ServersAvailable.NOSTR_BUILD) { + category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image" + } val fileName = randomChars() val extension = contentType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: "" @@ -73,16 +78,18 @@ object ImageUploader { .build() val requestBuilder = Request.Builder() - server.clientID()?.let { - requestBuilder.header("Authorization", it) + requestBuilder.addHeader("Authorization", it) + } + + if (serverType == ServersAvailable.NOSTR_BUILD) { + requestBuilder.addHeader("Content-Type", "multipart/form-data") } 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 { @@ -146,12 +153,11 @@ 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().replace("\"", "") } override fun clientID() = null 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..c008952dd 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 @@ -610,6 +610,7 @@ 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.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 921787a85..7fd0d6d94 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -333,6 +333,9 @@ 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 changing your image/video after you post From 7224ad4b05fc3dd79bae51bd8b1d1a0a7d31052e Mon Sep 17 00:00:00 2001 From: Believethehype Date: Wed, 3 May 2023 00:48:01 +0200 Subject: [PATCH 2/3] image upload works to nostr.build --- .../amethyst/ui/actions/ImageUploader.kt | 106 +++++++++++++----- .../amethyst/ui/actions/NewMediaModel.kt | 1 + .../amethyst/ui/actions/NewPostViewModel.kt | 1 + .../ui/actions/NewUserMetadataViewModel.kt | 1 + 4 files changed, 81 insertions(+), 28 deletions(-) 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 be6095797..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,13 +27,13 @@ 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" } @@ -41,10 +47,14 @@ object ImageUploader { ImgurServer() } - uploadImage(imageInputStream, contentType, myServer, server, 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, @@ -52,38 +62,47 @@ object ImageUploader { onSuccess: (String, String?) -> Unit, onError: (Throwable) -> Unit ) { - var category = "fileToUpload" - if (serverType != ServersAvailable.NOSTR_BUILD) { - category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image" - } + 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() - server.clientID()?.let { - requestBuilder.addHeader("Authorization", it) - } 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.addHeader("Authorization", it) } requestBuilder @@ -118,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? @@ -154,9 +205,8 @@ class ImgurServer : FileServer() { class NostrBuildServer : FileServer() { override fun postUrl(contentType: String?) = "https://nostr.build/api/upload/android.php" - override fun parseUrlFromSucess(body: String): String? { - val url = jacksonObjectMapper().readTree(body) + val url = jacksonObjectMapper().readTree(body) // return url.toString() return url.toString().replace("\"", "") } 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..b21629737 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 @@ -75,6 +75,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/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index fd38e3031..c5353035c 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,6 +145,7 @@ 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) { 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) From 67cc24c4d92b6ec0151bb7a5051cbc12468571ac Mon Sep 17 00:00:00 2001 From: Believethehype Date: Wed, 3 May 2023 18:13:18 +0200 Subject: [PATCH 3/3] added nostr.build NIP94 --- .../com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt | 2 ++ .../com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt | 2 ++ .../com/vitorpamplona/amethyst/ui/actions/NewPostView.kt | 3 +++ .../vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt | 2 +- app/src/main/res/values/strings.xml | 5 ++++- 5 files changed, 12 insertions(+), 2 deletions(-) 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 b21629737..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 } } 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 c008952dd..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 } @@ -613,6 +614,7 @@ fun ImageVideoDescription( 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)) ) @@ -742,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 c5353035c..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 @@ -148,7 +148,7 @@ open class NewPostViewModel : ViewModel() { 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/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5f2094740..cbc905e1d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -334,7 +334,7 @@ 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 + 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 @@ -342,6 +342,9 @@ 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