diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt index 3d2c1ff70..87a443de6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt @@ -23,6 +23,7 @@ import com.vitorpamplona.amethyst.service.NostrThreadDataSource import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource import com.vitorpamplona.amethyst.service.NostrVideoDataSource import com.vitorpamplona.amethyst.service.relays.Client +import com.vitorpamplona.amethyst.ui.actions.ImageUploader object ServiceManager { private var account: Account? = null @@ -66,6 +67,7 @@ object ServiceManager { NostrHomeDataSource.account = myAccount NostrChatroomListDataSource.account = myAccount NostrVideoDataSource.account = myAccount + ImageUploader.account = myAccount // Notification Elements NostrHomeDataSource.start() 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 c535d9934..aa10b7fb6 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 @@ -7,19 +7,24 @@ import android.webkit.MimeTypeMap import androidx.core.net.toFile import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.BuildConfig +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.toHexKey import com.vitorpamplona.amethyst.service.HttpClient +import com.vitorpamplona.amethyst.service.model.Event import okhttp3.* import okhttp3.MediaType.Companion.toMediaType import okio.BufferedSink +import okio.IOException import okio.source -import java.io.IOException import java.io.InputStream +import java.security.MessageDigest +import java.util.Base64 val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') fun randomChars() = List(16) { charPool.random() }.joinToString("") - object ImageUploader { + lateinit var account: Account fun uploadImage( uri: Uri, @@ -33,12 +38,11 @@ object ImageUploader { val myContentType = contentType ?: contentResolver.getType(uri) val imageInputStream = contentResolver.openInputStream(uri) - val length = size - ?: contentResolver.query(uri, null, null, null, null)?.use { - it.moveToFirst() - val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE) - it.getLong(sizeIndex) - } ?: kotlin.runCatching { + val length = size ?: contentResolver.query(uri, null, null, null, null)?.use { + it.moveToFirst() + val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE) + it.getLong(sizeIndex) + } ?: kotlin.runCatching { uri.toFile().length() }.getOrNull() ?: 0 @@ -58,6 +62,9 @@ object ImageUploader { ServersAvailable.NOSTRFILES_DEV, ServersAvailable.NOSTRFILES_DEV_NIP_94 -> { NostrFilesDevServer() } + ServersAvailable.NOSTRCHECK_ME, ServersAvailable.NOSTRCHECK_ME_NIP_94 -> { + NostrCheckMeServer() + } else -> { NostrBuildServer() } @@ -99,7 +106,7 @@ object ImageUploader { ) .build() - server.clientID()?.let { + server.clientID(requestBody.toString())?.let { requestBuilder.addHeader("Authorization", it) } @@ -133,6 +140,18 @@ object ImageUploader { } }) } + + fun NIP98Header(url: String, method: String, body: String): String { + var hash = "" + if (body != "") { + val sha256 = MessageDigest.getInstance("SHA-256") + hash = sha256.digest(body.toByteArray()).toHexKey() + } + var tags = listOf(listOf("u", url), listOf("method", method), listOf("payload", hash)) + var noteJson = (Event.create(account.loggedIn.privKey!!, 27235, tags, "")).toJson() + val encodedNIP98Event: String = Base64.getEncoder().encodeToString(noteJson.toByteArray()) + return "Nostr " + encodedNIP98Event + } } abstract class FileServer { @@ -140,7 +159,7 @@ abstract class FileServer { abstract fun parseUrlFromSuccess(body: String): String? abstract fun inputParameterName(contentType: String?): String - open fun clientID(): String? = null + open fun clientID(info: String): String? = null } class NostrImgServer : FileServer() { @@ -156,7 +175,7 @@ class NostrImgServer : FileServer() { return contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image" } - override fun clientID() = null + override fun clientID(info: String) = null } class ImgurServer : FileServer() { @@ -175,7 +194,7 @@ class ImgurServer : FileServer() { return contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image" } - override fun clientID() = "Client-ID e6aea87296f3f96" + override fun clientID(info: String) = "Client-ID e6aea87296f3f96" } class NostrBuildServer : FileServer() { @@ -189,7 +208,7 @@ class NostrBuildServer : FileServer() { return "fileToUpload" } - override fun clientID() = null + override fun clientID(info: String) = null } class NostrFilesDevServer : FileServer() { @@ -203,5 +222,43 @@ class NostrFilesDevServer : FileServer() { return "file" } - override fun clientID() = null + override fun clientID(info: String) = null +} + +class NostrCheckMeServer : FileServer() { + override fun postUrl(contentType: String?) = "https://nostrcheck.me/api/v1/media" + override fun parseUrlFromSuccess(body: String): String? { + val tree = jacksonObjectMapper().readTree(body) + val url = tree?.get("url")?.asText() + var id = tree?.get("id")?.asText() + var isCompleted = false + + val client = OkHttpClient() + var requrl = "https://nostrcheck.me/api/v1/media?id=" + id // + "&apikey=26d075787d261660682fb9d20dbffa538c708b1eda921d0efa2be95fbef4910a" + + val request = Request.Builder() + .url(requrl) + .addHeader("Authorization", ImageUploader.NIP98Header(requrl, "GET", "")) + .get() + .build() + + while (!isCompleted) { + val response = client.newCall(request).execute() + val body = response.body?.string() + val tree = jacksonObjectMapper().readTree(body) + isCompleted = tree?.get("status")?.asText() == "completed" + try { + Thread.sleep(500) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + return url + } + + override fun inputParameterName(contentType: String?): String { + return "mediafile" + } + + override fun clientID(body: String) = ImageUploader.NIP98Header("https://nostrcheck.me/api/v1/media", "POST", body) } 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 ac3a7b403..63cee37b9 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 @@ -50,6 +50,8 @@ open class NewMediaModel : ViewModel() { selectedServer = ServersAvailable.NOSTR_BUILD_NIP_94 } else if (selectedServer == ServersAvailable.NOSTRFILES_DEV) { selectedServer = ServersAvailable.NOSTRFILES_DEV_NIP_94 + } else if (selectedServer == ServersAvailable.NOSTRCHECK_ME) { + selectedServer = ServersAvailable.NOSTRCHECK_ME_NIP_94 } } 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 16cbe60d9..79431edd0 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 @@ -798,11 +798,13 @@ enum class ServersAvailable { NOSTR_BUILD, NOSTRIMG, NOSTRFILES_DEV, + NOSTRCHECK_ME, // IMGUR_NIP_94, NOSTRIMG_NIP_94, NOSTR_BUILD_NIP_94, NOSTRFILES_DEV_NIP_94, + NOSTRCHECK_ME_NIP_94, NIP95 } @@ -825,10 +827,12 @@ fun ImageVideoDescription( 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.NOSTRFILES_DEV, stringResource(id = R.string.upload_server_nostrfilesdev), stringResource(id = R.string.upload_server_nostrfilesdev_explainer)), + Triple(ServersAvailable.NOSTRCHECK_ME, stringResource(id = R.string.upload_server_nostrcheckme), stringResource(id = R.string.upload_server_nostrcheckme_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.NOSTRFILES_DEV_NIP_94, stringResource(id = R.string.upload_server_nostrfilesdev_nip94), stringResource(id = R.string.upload_server_nostrfilesdev_nip94_explainer)), + Triple(ServersAvailable.NOSTRCHECK_ME_NIP_94, stringResource(id = R.string.upload_server_nostrcheckme_nip94), stringResource(id = R.string.upload_server_nostrcheckme_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/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 87b3ef89c..5a198f062 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -383,9 +383,11 @@ fun EditFieldRow( ServersAvailable.NOSTR_BUILD -> ServersAvailable.NOSTR_BUILD ServersAvailable.NOSTRIMG -> ServersAvailable.NOSTRIMG ServersAvailable.NOSTRFILES_DEV -> ServersAvailable.NOSTRFILES_DEV + ServersAvailable.NOSTRCHECK_ME -> ServersAvailable.NOSTRCHECK_ME ServersAvailable.NOSTR_BUILD_NIP_94 -> ServersAvailable.NOSTR_BUILD ServersAvailable.NOSTRIMG_NIP_94 -> ServersAvailable.NOSTRIMG ServersAvailable.NOSTRFILES_DEV_NIP_94 -> ServersAvailable.NOSTRFILES_DEV + ServersAvailable.NOSTRCHECK_ME_NIP_94 -> ServersAvailable.NOSTRCHECK_ME ServersAvailable.NIP95 -> ServersAvailable.NOSTR_BUILD } } else { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f78d10a78..c061fab0e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -338,6 +338,9 @@ nostrfiles.dev - trusted Nostrfiles.dev can modify the file + nostrcheck.me - trusted + nostrcheck.me can modify the file + Verifiable Imgur (NIP-94) Checks if Imgur modified the file. New NIP: other clients might not see it @@ -351,6 +354,11 @@ Verifiable Nostrfiles.dev (NIP-94) Checks if Nostrfiles.dev modified the file. New NIP: other clients might not see it + Verifiable Nostrcheck.me (NIP-94) + Checks if Nostrcheck.me modified the file. New NIP: other clients might not see it + + + Your relays (NIP-95) Files are hosted by your relays. New NIP: check if they support