mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
fix(marmot): new groups carry the encrypted-media policy so White Noise can send media
Groups created here left the encrypted-media-v2 component (0x800b) off, and White Noise (MDK) refuses to send any attachment in a group without it: InvalidMediaReference, 'group does not require encrypted media'. MDK treats the component's presence as the requirement. After the initial metadata commit, while the creator is still the only member, commit the policy with the account's Blossom servers. Joiners get it in their Welcome, so no member ever has to apply a later commit before sharing media. The policy names the account's own Blossom servers, or, for an account without a list, the default list the upload picker offers and falls back through (the Group Info "Use encrypted attachments" button now works for those accounts too, instead of asking them to add a server first). Best-effort: if the commit fails the group is still created, and text and legacy MIP-04 media keep working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a7ae40242a
commit
9d856315ad
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.model
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType
|
||||
import com.vitorpamplona.quartz.marmot.appComponents.BlobStoreEndpointV2
|
||||
import com.vitorpamplona.quartz.marmot.appComponents.EncryptedMediaPolicyV2
|
||||
import com.vitorpamplona.quartz.marmot.appComponents.GroupAvatarUrlV1
|
||||
@@ -962,13 +963,40 @@ class AccountMarmotActions(
|
||||
*
|
||||
* The endpoints come from the account's own Blossom server list, because a
|
||||
* policy naming servers the uploader does not use would describe a group
|
||||
* nobody can actually post media to.
|
||||
* nobody can actually post media to. An account without one uploads to the
|
||||
* default list (the same one the upload picker offers and falls back
|
||||
* through), so that is what its policy names.
|
||||
*/
|
||||
fun marmotMediaPolicyServers(): List<String> =
|
||||
account.blossomServers.flow.value
|
||||
.ifEmpty {
|
||||
account.blossomServers.hostNameFlow.value
|
||||
.filter { it.type == ServerType.Blossom }
|
||||
.map { it.baseUrl }
|
||||
}.mapNotNull { normalizedPolicyBaseUrl(it) }
|
||||
.distinct()
|
||||
.take(EncryptedMediaPolicyV2.MAX_ENTRIES)
|
||||
|
||||
/**
|
||||
* [url] in the byte-exact form the media policy requires, or null when it has none.
|
||||
* The component rejects a base URL that isn't its own WHATWG serialization, so the
|
||||
* everyday spelling without a trailing slash (`https://cdn.nostrcheck.me`) failed the
|
||||
* whole commit.
|
||||
*/
|
||||
private fun normalizedPolicyBaseUrl(url: String): String? =
|
||||
try {
|
||||
MarmotWebUrl.normalize(url, allowHttp = true, label = "base_url").also {
|
||||
EncryptedMediaPolicyV2.requireNormalizedBaseUrl(it)
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
|
||||
suspend fun enableMarmotEncryptedMediaV2(nostrGroupId: HexKey) {
|
||||
val manager = account.marmotManager ?: return
|
||||
if (!account.isWriteable()) return
|
||||
|
||||
val servers = account.blossomServers.flow.value
|
||||
val servers = marmotMediaPolicyServers()
|
||||
require(servers.isNotEmpty()) {
|
||||
"Cannot enable encrypted media without at least one Blossom server configured"
|
||||
}
|
||||
|
||||
+1
-3
@@ -2537,9 +2537,7 @@ class AccountViewModel(
|
||||
fun marmotUsesEncryptedMediaV2(nostrGroupId: String): Boolean = account.marmotManager?.encryptedMediaPolicy(nostrGroupId) != null
|
||||
|
||||
/** True when this account has somewhere to upload a group's encrypted media. */
|
||||
fun hasBlossomServers(): Boolean =
|
||||
account.blossomServers.flow.value
|
||||
.isNotEmpty()
|
||||
fun hasBlossomServers(): Boolean = account.marmot.marmotMediaPolicyServers().isNotEmpty()
|
||||
|
||||
suspend fun enableMarmotEncryptedMediaV2(nostrGroupId: String) {
|
||||
account.marmot.enableMarmotEncryptedMediaV2(nostrGroupId)
|
||||
|
||||
+13
@@ -67,9 +67,11 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconChange
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
@Composable
|
||||
fun CreateGroupScreen(
|
||||
@@ -136,6 +138,17 @@ fun CreateGroupScreen(
|
||||
description = groupDescription.trim(),
|
||||
icon = iconChange,
|
||||
)
|
||||
// Commit the encrypted-media policy while we are the only member. White Noise
|
||||
// (MDK) refuses to send any attachment in a group without one
|
||||
// ("group does not require encrypted media"), and joiners learn it from the
|
||||
// Welcome, so nobody has to apply a later commit to be able to share media.
|
||||
// Best-effort: a group without it still works for text and legacy MIP-04.
|
||||
try {
|
||||
accountViewModel.enableMarmotEncryptedMediaV2(nostrGroupId)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.w("CreateGroupScreen") { "Could not enable encrypted media for $nostrGroupId: ${e.message}" }
|
||||
}
|
||||
nav.popUpTo(Route.MarmotGroupChat(nostrGroupId), Route.CreateMarmotGroup::class)
|
||||
} catch (e: Exception) {
|
||||
isCreating = false
|
||||
|
||||
Reference in New Issue
Block a user