Minimizes the dependency on the Account further by moving Tor settings to a tor Privacy State
This commit is contained in:
@@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListState
|
||||
import com.vitorpamplona.amethyst.model.nip78AppSpecific.AppSpecificState
|
||||
import com.vitorpamplona.amethyst.model.nip96FileStorage.FileStorageServerListState
|
||||
import com.vitorpamplona.amethyst.model.nipB7Blossom.BlossomServerListState
|
||||
import com.vitorpamplona.amethyst.model.privacyOptions.PrivacyState
|
||||
import com.vitorpamplona.amethyst.model.serverList.MergedFollowListsState
|
||||
import com.vitorpamplona.amethyst.model.serverList.MergedFollowPlusMineRelayListsState
|
||||
import com.vitorpamplona.amethyst.model.serverList.MergedServerListState
|
||||
@@ -79,9 +80,8 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxLoaderState
|
||||
import com.vitorpamplona.amethyst.model.torState.TorRelayState
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
import com.vitorpamplona.amethyst.service.ots.OtsResolverBuilder
|
||||
import com.vitorpamplona.amethyst.service.ots.OkHttpOtsResolverBuilder
|
||||
import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorType
|
||||
import com.vitorpamplona.quartz.experimental.bounties.BountyAddValueEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
|
||||
@@ -118,7 +118,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.downloadFirst
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
|
||||
@@ -130,7 +129,6 @@ import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.hasAnyTaggedUser
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.references.references
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDMCache
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.reply
|
||||
@@ -302,14 +300,17 @@ class Account(
|
||||
|
||||
val chatroomList = LocalCache.getOrCreateChatroomList(signer.pubKey)
|
||||
|
||||
val otsResolver: OtsResolver =
|
||||
OtsResolverBuilder().build(
|
||||
val privacyState = PrivacyState(settings)
|
||||
val torRelayState = TorRelayState(trustedRelays, dmRelayList, settings, scope)
|
||||
|
||||
val otsResolverBuilder: OkHttpOtsResolverBuilder =
|
||||
OkHttpOtsResolverBuilder(
|
||||
Amethyst.instance.okHttpClients,
|
||||
::shouldUseTorForMoneyOperations,
|
||||
privacyState::shouldUseTorForMoneyOperations,
|
||||
Amethyst.instance.otsBlockHeightCache,
|
||||
)
|
||||
|
||||
val otsState = OtsState(signer, cache, otsResolver, scope, settings)
|
||||
val otsState = OtsState(signer, cache, otsResolverBuilder, scope, settings)
|
||||
|
||||
val feedDecryptionCaches =
|
||||
FeedDecryptionCaches(
|
||||
@@ -389,8 +390,6 @@ class Account(
|
||||
|
||||
*/
|
||||
|
||||
val torRelayState = TorRelayState(trustedRelays, dmRelayList, settings, scope)
|
||||
|
||||
fun isWriteable(): Boolean = settings.isWriteable()
|
||||
|
||||
suspend fun updateWarnReports(warnReports: Boolean): Boolean {
|
||||
@@ -463,67 +462,15 @@ class Account(
|
||||
|
||||
private suspend fun sendNewAppSpecificData() = sendMyPublicAndPrivateOutbox(appSpecific.saveNewAppSpecificData())
|
||||
|
||||
suspend fun sendNewUserMetadata(
|
||||
name: String? = null,
|
||||
picture: String? = null,
|
||||
banner: String? = null,
|
||||
website: String? = null,
|
||||
pronouns: String? = null,
|
||||
about: String? = null,
|
||||
nip05: String? = null,
|
||||
lnAddress: String? = null,
|
||||
lnURL: String? = null,
|
||||
twitter: String? = null,
|
||||
mastodon: String? = null,
|
||||
github: String? = null,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
val userMetadataEvent =
|
||||
userMetadata.sendNewUserMetadata(
|
||||
name,
|
||||
picture,
|
||||
banner,
|
||||
website,
|
||||
pronouns,
|
||||
about,
|
||||
nip05,
|
||||
lnAddress,
|
||||
lnURL,
|
||||
twitter,
|
||||
mastodon,
|
||||
github,
|
||||
)
|
||||
|
||||
sendLiterallyEverywhere(userMetadataEvent)
|
||||
}
|
||||
|
||||
fun reactionTo(
|
||||
note: Note,
|
||||
reaction: String,
|
||||
): List<Note> = note.reactedBy(userProfile(), reaction)
|
||||
|
||||
fun hasBoosted(note: Note): Boolean = boostsTo(note).isNotEmpty()
|
||||
|
||||
fun boostsTo(note: Note): List<Note> = note.boostedBy(userProfile())
|
||||
|
||||
fun hasReacted(
|
||||
note: Note,
|
||||
reaction: String,
|
||||
): Boolean = note.hasReacted(userProfile(), reaction)
|
||||
|
||||
suspend fun reactTo(
|
||||
note: Note,
|
||||
reaction: String,
|
||||
) = ReactionAction.reactTo(
|
||||
note,
|
||||
reaction,
|
||||
userProfile(),
|
||||
signer,
|
||||
onPublic = {
|
||||
client.send(it, computeMyReactionToNote(note, it))
|
||||
cache.justConsumeMyOwnEvent(it)
|
||||
},
|
||||
note = note,
|
||||
reaction = reaction,
|
||||
by = userProfile(),
|
||||
signer = signer,
|
||||
onPublic = ::sendAutomatic,
|
||||
onPrivate = ::broadcastPrivately,
|
||||
)
|
||||
|
||||
@@ -534,18 +481,15 @@ class Account(
|
||||
zapType: LnZapEvent.ZapType,
|
||||
toUser: User?,
|
||||
additionalRelays: Set<NormalizedRelayUrl>? = null,
|
||||
): LnZapRequestEvent {
|
||||
val relays = nip65RelayList.inboxFlow.value + (additionalRelays ?: emptySet())
|
||||
return LnZapRequestEvent.create(
|
||||
event,
|
||||
relays = relays.mapTo(mutableSetOf()) { it.url },
|
||||
signer,
|
||||
pollOption,
|
||||
message,
|
||||
zapType,
|
||||
toUser?.pubkeyHex,
|
||||
)
|
||||
}
|
||||
) = LnZapRequestEvent.create(
|
||||
zappedEvent = event,
|
||||
relays = nip65RelayList.inboxFlow.value + (additionalRelays ?: emptySet()),
|
||||
signer = signer,
|
||||
pollOption = pollOption,
|
||||
message = message,
|
||||
zapType = zapType,
|
||||
toUserPubHex = toUser?.pubkeyHex,
|
||||
)
|
||||
|
||||
suspend fun calculateIfNoteWasZappedByAccount(
|
||||
zappedNote: Note?,
|
||||
@@ -570,12 +514,10 @@ class Account(
|
||||
message: String = "",
|
||||
zapType: LnZapEvent.ZapType,
|
||||
): LnZapRequestEvent {
|
||||
val relays = nip65RelayList.inboxFlow.value + user.inboxRelays()
|
||||
|
||||
val zapRequest =
|
||||
LnZapRequestEvent.create(
|
||||
userHex = user.pubkeyHex,
|
||||
relays = relays,
|
||||
relays = nip65RelayList.inboxFlow.value + user.inboxRelays(),
|
||||
signer = signer,
|
||||
message = message,
|
||||
zapType = zapType,
|
||||
@@ -596,9 +538,7 @@ class Account(
|
||||
type: ReportType,
|
||||
) = sendMyPublicAndPrivateOutbox(ReportAction.report(user, type, userProfile(), signer))
|
||||
|
||||
suspend fun delete(note: Note) {
|
||||
delete(listOf(note))
|
||||
}
|
||||
suspend fun delete(note: Note) = delete(listOf(note))
|
||||
|
||||
suspend fun delete(notes: List<Note>) {
|
||||
if (!isWriteable()) return
|
||||
@@ -627,10 +567,7 @@ class Account(
|
||||
if (!isWriteable()) return
|
||||
if (event.pubKey != signer.pubKey) return
|
||||
|
||||
val deletionEvent =
|
||||
signer.sign(
|
||||
DeletionEvent.build(listOf(event)),
|
||||
)
|
||||
val deletionEvent = signer.sign(DeletionEvent.build(listOf(event)))
|
||||
client.send(deletionEvent, outboxRelays.flow.value + additionalRelays)
|
||||
cache.justConsumeMyOwnEvent(deletionEvent)
|
||||
}
|
||||
@@ -1822,104 +1759,6 @@ class Account(
|
||||
|
||||
fun markDonatedInThisVersion() = settings.markDonatedInThisVersion(BuildConfig.VERSION_NAME)
|
||||
|
||||
fun shouldUseTorForImageDownload(url: String) =
|
||||
shouldUseTorFor(
|
||||
url,
|
||||
settings.torSettings.torType.value,
|
||||
settings.torSettings.imagesViaTor.value,
|
||||
)
|
||||
|
||||
fun shouldUseTorFor(
|
||||
url: String,
|
||||
torType: TorType,
|
||||
imagesViaTor: Boolean,
|
||||
) = when (torType) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> shouldUseTor(url, imagesViaTor)
|
||||
TorType.EXTERNAL -> shouldUseTor(url, imagesViaTor)
|
||||
}
|
||||
|
||||
private fun shouldUseTor(
|
||||
normalizedUrl: String,
|
||||
final: Boolean,
|
||||
): Boolean =
|
||||
if (RelayUrlNormalizer.isLocalHost(normalizedUrl)) {
|
||||
false
|
||||
} else if (RelayUrlNormalizer.isOnion(normalizedUrl)) {
|
||||
true
|
||||
} else {
|
||||
final
|
||||
}
|
||||
|
||||
fun shouldUseTorForVideoDownload() =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> settings.torSettings.videosViaTor.value
|
||||
TorType.EXTERNAL -> settings.torSettings.videosViaTor.value
|
||||
}
|
||||
|
||||
fun shouldUseTorForVideoDownload(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.videosViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.videosViaTor.value)
|
||||
}
|
||||
|
||||
fun shouldUseTorForPreviewUrl(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.urlPreviewsViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.urlPreviewsViaTor.value)
|
||||
}
|
||||
|
||||
fun shouldUseTorForTrustedRelays() =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> settings.torSettings.trustedRelaysViaTor.value
|
||||
TorType.EXTERNAL -> settings.torSettings.trustedRelaysViaTor.value
|
||||
}
|
||||
|
||||
fun shouldUseTorForClean(relay: NormalizedRelayUrl) = torRelayState.flow.value.useTor(relay)
|
||||
|
||||
private fun checkLocalHostOnionAndThen(
|
||||
url: String,
|
||||
final: Boolean,
|
||||
): Boolean = checkLocalHostOnionAndThen(url, settings.torSettings.onionRelaysViaTor.value, final)
|
||||
|
||||
private fun checkLocalHostOnionAndThen(
|
||||
normalizedUrl: String,
|
||||
isOnionRelaysActive: Boolean,
|
||||
final: Boolean,
|
||||
): Boolean =
|
||||
if (RelayUrlNormalizer.isLocalHost(normalizedUrl)) {
|
||||
false
|
||||
} else if (RelayUrlNormalizer.isOnion(normalizedUrl)) {
|
||||
isOnionRelaysActive
|
||||
} else {
|
||||
final
|
||||
}
|
||||
|
||||
fun shouldUseTorForMoneyOperations(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.moneyOperationsViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.moneyOperationsViaTor.value)
|
||||
}
|
||||
|
||||
fun shouldUseTorForNIP05(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip05VerificationsViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip05VerificationsViaTor.value)
|
||||
}
|
||||
|
||||
fun shouldUseTorForNIP96(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
||||
}
|
||||
|
||||
init {
|
||||
Log.d("AccountRegisterObservers", "Init")
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUsers
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationState
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
@@ -2102,7 +2102,7 @@ object LocalCache : ILocalCache {
|
||||
|
||||
suspend fun findEarliestOtsForNote(
|
||||
note: Note,
|
||||
resolverBuilder: () -> OtsResolver,
|
||||
resolverBuilder: OtsResolverBuilder,
|
||||
): Long? {
|
||||
checkNotInMainThread()
|
||||
|
||||
|
||||
@@ -745,14 +745,14 @@ open class Note(
|
||||
fun hasReacted(
|
||||
loggedIn: User,
|
||||
content: String,
|
||||
): Boolean = reactedBy(loggedIn, content).isNotEmpty()
|
||||
): Boolean = allReactionsOfContentByAuthor(loggedIn, content).isNotEmpty()
|
||||
|
||||
fun reactedBy(
|
||||
fun allReactionsOfContentByAuthor(
|
||||
loggedIn: User,
|
||||
content: String,
|
||||
): List<Note> = reactions[content]?.filter { it.author == loggedIn } ?: emptyList()
|
||||
|
||||
fun reactedBy(loggedIn: User): List<String> = reactions.filter { it.value.any { it.author == loggedIn } }.mapNotNull { it.key }
|
||||
fun allReactionsByAuthor(loggedIn: User): List<String> = reactions.filter { it.value.any { it.author == loggedIn } }.mapNotNull { it.key }
|
||||
|
||||
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
||||
val fiveMinsAgo = TimeUtils.fiveMinutesAgo()
|
||||
|
||||
@@ -24,10 +24,10 @@ import android.util.Log
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.ots.OkHttpOtsResolverBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.update
|
||||
import java.util.Base64
|
||||
@@ -35,7 +35,7 @@ import java.util.Base64
|
||||
class OtsState(
|
||||
val signer: NostrSigner,
|
||||
val cache: LocalCache,
|
||||
val otsResolver: OtsResolver,
|
||||
val otsResolver: OkHttpOtsResolverBuilder,
|
||||
val scope: CoroutineScope,
|
||||
val settings: AccountSettings,
|
||||
) {
|
||||
@@ -43,7 +43,7 @@ class OtsState(
|
||||
Log.d("Pending Attestations", "Updating ${settings.pendingAttestations.value.size} pending attestations")
|
||||
|
||||
return settings.pendingAttestations.value.toList().mapNotNull { (key, value) ->
|
||||
val otsState = OtsEvent.upgrade(Base64.getDecoder().decode(value), key, otsResolver)
|
||||
val otsState = OtsEvent.upgrade(Base64.getDecoder().decode(value), key, otsResolver.build())
|
||||
|
||||
if (otsState != null) {
|
||||
val hint = cache.getNoteIfExists(key)?.toEventHint<Event>()
|
||||
@@ -75,6 +75,15 @@ class OtsState(
|
||||
|
||||
val id = note.event?.id ?: note.idHex
|
||||
|
||||
settings.addPendingAttestation(id, Base64.getEncoder().encodeToString(OtsEvent.stamp(id, otsResolver)))
|
||||
settings.addPendingAttestation(
|
||||
id = id,
|
||||
stamp =
|
||||
Base64.getEncoder().encodeToString(
|
||||
OtsEvent.stamp(
|
||||
id,
|
||||
otsResolver.build(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.privacyOptions
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorType
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
|
||||
class PrivacyState(
|
||||
val settings: AccountSettings,
|
||||
) {
|
||||
fun shouldUseTorForImageDownload(url: String) =
|
||||
shouldUseTorFor(
|
||||
url,
|
||||
settings.torSettings.torType.value,
|
||||
settings.torSettings.imagesViaTor.value,
|
||||
)
|
||||
|
||||
fun shouldUseTorFor(
|
||||
url: String,
|
||||
torType: TorType,
|
||||
imagesViaTor: Boolean,
|
||||
) = when (torType) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> shouldUseTor(url, imagesViaTor)
|
||||
TorType.EXTERNAL -> shouldUseTor(url, imagesViaTor)
|
||||
}
|
||||
|
||||
private fun shouldUseTor(
|
||||
normalizedUrl: String,
|
||||
final: Boolean,
|
||||
): Boolean =
|
||||
if (RelayUrlNormalizer.isLocalHost(normalizedUrl)) {
|
||||
false
|
||||
} else if (RelayUrlNormalizer.isOnion(normalizedUrl)) {
|
||||
true
|
||||
} else {
|
||||
final
|
||||
}
|
||||
|
||||
fun shouldUseTorForVideoDownload() =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> settings.torSettings.videosViaTor.value
|
||||
TorType.EXTERNAL -> settings.torSettings.videosViaTor.value
|
||||
}
|
||||
|
||||
fun shouldUseTorForVideoDownload(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.videosViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.videosViaTor.value)
|
||||
}
|
||||
|
||||
fun shouldUseTorForPreviewUrl(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.urlPreviewsViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.urlPreviewsViaTor.value)
|
||||
}
|
||||
|
||||
fun shouldUseTorForTrustedRelays() =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> settings.torSettings.trustedRelaysViaTor.value
|
||||
TorType.EXTERNAL -> settings.torSettings.trustedRelaysViaTor.value
|
||||
}
|
||||
|
||||
private fun checkLocalHostOnionAndThen(
|
||||
url: String,
|
||||
final: Boolean,
|
||||
): Boolean = checkLocalHostOnionAndThen(url, settings.torSettings.onionRelaysViaTor.value, final)
|
||||
|
||||
private fun checkLocalHostOnionAndThen(
|
||||
normalizedUrl: String,
|
||||
isOnionRelaysActive: Boolean,
|
||||
final: Boolean,
|
||||
): Boolean =
|
||||
if (RelayUrlNormalizer.isLocalHost(normalizedUrl)) {
|
||||
false
|
||||
} else if (RelayUrlNormalizer.isOnion(normalizedUrl)) {
|
||||
isOnionRelaysActive
|
||||
} else {
|
||||
final
|
||||
}
|
||||
|
||||
fun shouldUseTorForMoneyOperations(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.moneyOperationsViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.moneyOperationsViaTor.value)
|
||||
}
|
||||
|
||||
fun shouldUseTorForNIP05(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip05VerificationsViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip05VerificationsViaTor.value)
|
||||
}
|
||||
|
||||
fun shouldUseTorForUploads(url: String) =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
||||
}
|
||||
}
|
||||
@@ -65,10 +65,6 @@ class ChatroomList(
|
||||
}
|
||||
}
|
||||
|
||||
fun createChatroom(withKey: ChatroomKey) {
|
||||
getOrCreatePrivateChatroom(withKey)
|
||||
}
|
||||
|
||||
fun removeMessage(
|
||||
user: User,
|
||||
msg: Note,
|
||||
|
||||
@@ -115,4 +115,6 @@ class TorRelayState(
|
||||
dmRelayList = dmRelayState.flow.value,
|
||||
),
|
||||
)
|
||||
|
||||
fun shouldUseTorForClean(relay: NormalizedRelayUrl) = flow.value.useTor(relay)
|
||||
}
|
||||
|
||||
+17
-14
@@ -22,8 +22,13 @@ package com.vitorpamplona.amethyst.service.ots
|
||||
|
||||
import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
|
||||
|
||||
class OtsResolverBuilder {
|
||||
class OkHttpOtsResolverBuilder(
|
||||
val okHttpClients: DualHttpClientManager,
|
||||
val shouldUseTorForUrl: (String) -> Boolean,
|
||||
val cache: OtsBlockHeightCache,
|
||||
) : OtsResolverBuilder {
|
||||
fun getAPI(usingTor: Boolean) =
|
||||
if (usingTor) {
|
||||
OkHttpBitcoinExplorer.MEMPOOL_API_URL
|
||||
@@ -31,22 +36,20 @@ class OtsResolverBuilder {
|
||||
OkHttpBitcoinExplorer.BLOCKSTREAM_API_URL
|
||||
}
|
||||
|
||||
fun build(
|
||||
okHttpClients: DualHttpClientManager,
|
||||
shouldUseTorForUrl: (String) -> Boolean,
|
||||
cache: OtsBlockHeightCache,
|
||||
): OtsResolver {
|
||||
override fun build(): OtsResolver {
|
||||
val shouldUseTor = shouldUseTorForUrl(OkHttpBitcoinExplorer.MEMPOOL_API_URL)
|
||||
|
||||
return OtsResolver(
|
||||
OkHttpBitcoinExplorer(
|
||||
getAPI(shouldUseTor),
|
||||
okHttpClients.getHttpClient(shouldUseTor),
|
||||
cache,
|
||||
),
|
||||
OkHttpCalendarBuilder {
|
||||
okHttpClients.getHttpClient(shouldUseTorForUrl(it))
|
||||
},
|
||||
explorer =
|
||||
OkHttpBitcoinExplorer(
|
||||
baseAPI = getAPI(usingTor = shouldUseTor),
|
||||
client = okHttpClients.getHttpClient(shouldUseTor),
|
||||
cache = cache,
|
||||
),
|
||||
calendarBuilder =
|
||||
OkHttpCalendarBuilder {
|
||||
okHttpClients.getHttpClient(shouldUseTorForUrl(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -152,7 +152,7 @@ class UploadOrchestrator {
|
||||
alt = alt,
|
||||
sensitiveContent = contentWarningReason,
|
||||
serverBaseUrl = serverBaseUrl,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||
onProgress = { percent: Float ->
|
||||
updateState(0.2 + (0.2 * percent), UploadingState.Uploading)
|
||||
},
|
||||
@@ -165,7 +165,7 @@ class UploadOrchestrator {
|
||||
localContentType = contentType,
|
||||
originalContentType = contentTypeForResult,
|
||||
originalHash = originalHash,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||
)
|
||||
} catch (_: SignerExceptions.ReadOnlyException) {
|
||||
error(R.string.login_with_a_private_key_to_be_able_to_upload)
|
||||
@@ -198,7 +198,7 @@ class UploadOrchestrator {
|
||||
alt = alt,
|
||||
sensitiveContent = contentWarningReason,
|
||||
serverBaseUrl = serverBaseUrl,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||
httpAuth = account::createBlossomUploadAuth,
|
||||
context = context,
|
||||
)
|
||||
@@ -206,7 +206,7 @@ class UploadOrchestrator {
|
||||
verifyHeader(
|
||||
uploadResult = result,
|
||||
localContentType = contentType,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||
originalHash = originalHash,
|
||||
originalContentType = contentTypeForResult,
|
||||
)
|
||||
|
||||
+3
-2
@@ -58,10 +58,11 @@ fun NewUserMetadataScreen(
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val postViewModel: NewUserMetadataViewModel = viewModel()
|
||||
postViewModel.init(accountViewModel)
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
postViewModel.load(accountViewModel.account)
|
||||
LaunchedEffect(accountViewModel) {
|
||||
postViewModel.load()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
|
||||
+30
-24
@@ -25,7 +25,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
@@ -35,17 +34,17 @@ import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
||||
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.GitHubIdentity
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.MastodonIdentity
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.TwitterIdentity
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.identityClaims
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
class NewUserMetadataViewModel : ViewModel() {
|
||||
private lateinit var accountViewModel: AccountViewModel
|
||||
private lateinit var account: Account
|
||||
|
||||
// val userName = mutableStateOf("")
|
||||
@@ -68,9 +67,12 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
var isUploadingImageForPicture by mutableStateOf(false)
|
||||
var isUploadingImageForBanner by mutableStateOf(false)
|
||||
|
||||
fun load(account: Account) {
|
||||
this.account = account
|
||||
fun init(accountViewModel: AccountViewModel) {
|
||||
this.accountViewModel = accountViewModel
|
||||
this.account = accountViewModel.account
|
||||
}
|
||||
|
||||
fun load() {
|
||||
account.userProfile().let {
|
||||
// userName.value = it.bestUsername() ?: ""
|
||||
displayName.value = it.info?.bestName() ?: ""
|
||||
@@ -100,21 +102,25 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
|
||||
fun create() {
|
||||
// Tries to not delete any existing attribute that we do not work with.
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
account.sendNewUserMetadata(
|
||||
name = displayName.value,
|
||||
picture = picture.value,
|
||||
banner = banner.value,
|
||||
website = website.value,
|
||||
pronouns = pronouns.value,
|
||||
about = about.value,
|
||||
nip05 = nip05.value,
|
||||
lnAddress = lnAddress.value,
|
||||
lnURL = lnURL.value,
|
||||
twitter = twitter.value,
|
||||
mastodon = mastodon.value,
|
||||
github = github.value,
|
||||
)
|
||||
accountViewModel.runIOCatching {
|
||||
val metadata =
|
||||
account.userMetadata.sendNewUserMetadata(
|
||||
name = displayName.value,
|
||||
picture = picture.value,
|
||||
banner = banner.value,
|
||||
website = website.value,
|
||||
pronouns = pronouns.value,
|
||||
about = about.value,
|
||||
nip05 = nip05.value,
|
||||
lnAddress = lnAddress.value,
|
||||
lnURL = lnURL.value,
|
||||
twitter = twitter.value,
|
||||
mastodon = mastodon.value,
|
||||
github = github.value,
|
||||
)
|
||||
|
||||
account.sendLiterallyEverywhere(metadata)
|
||||
|
||||
clear()
|
||||
}
|
||||
}
|
||||
@@ -139,7 +145,7 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
context: Context,
|
||||
onError: (String, String) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
accountViewModel.runIOCatching {
|
||||
upload(
|
||||
uri,
|
||||
context,
|
||||
@@ -155,7 +161,7 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
context: Context,
|
||||
onError: (String, String) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
accountViewModel.runIOCatching {
|
||||
upload(
|
||||
uri,
|
||||
context,
|
||||
@@ -187,7 +193,7 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
alt = null,
|
||||
sensitiveContent = null,
|
||||
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||
onProgress = {},
|
||||
httpAuth = account::createHTTPAuthorization,
|
||||
context = context,
|
||||
@@ -200,7 +206,7 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
alt = null,
|
||||
sensitiveContent = null,
|
||||
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||
httpAuth = account::createBlossomUploadAuth,
|
||||
context = context,
|
||||
)
|
||||
|
||||
+3
-3
@@ -79,7 +79,7 @@ fun routeFor(
|
||||
}
|
||||
} else if (innerEvent is ChatroomKeyable) {
|
||||
val room = innerEvent.chatroomKey(loggedIn.userProfile().pubkeyHex)
|
||||
loggedIn.chatroomList.createChatroom(room)
|
||||
loggedIn.chatroomList.getOrCreatePrivateChatroom(room)
|
||||
return Route.Room(room)
|
||||
} else if (innerEvent is AddressableEvent) {
|
||||
return Route.Note(noteEvent.aTag().toTag())
|
||||
@@ -104,7 +104,7 @@ fun routeFor(
|
||||
}
|
||||
} else if (noteEvent is ChatroomKeyable) {
|
||||
val room = noteEvent.chatroomKey(loggedIn.userProfile().pubkeyHex)
|
||||
loggedIn.chatroomList.createChatroom(room)
|
||||
loggedIn.chatroomList.getOrCreatePrivateChatroom(room)
|
||||
return Route.Room(room)
|
||||
} else if (noteEvent is CommunityDefinitionEvent) {
|
||||
return Route.Community(noteEvent.kind, noteEvent.pubKey, noteEvent.dTag())
|
||||
@@ -169,7 +169,7 @@ fun routeToMessage(
|
||||
draftId: HexKey? = null,
|
||||
account: Account,
|
||||
): Route {
|
||||
account.chatroomList.createChatroom(room)
|
||||
account.chatroomList.getOrCreatePrivateChatroom(room)
|
||||
|
||||
return Route.Room(room, draftMessage, replyId, draftId)
|
||||
}
|
||||
|
||||
@@ -1355,7 +1355,7 @@ fun ReactionChoicePopup(
|
||||
val iconSizePx = with(LocalDensity.current) { -iconSize.toPx().toInt() }
|
||||
|
||||
val reactions by accountViewModel.reactionChoicesFlow().collectAsStateWithLifecycle()
|
||||
val toRemove = remember { baseNote.reactedBy(accountViewModel.userProfile()).toImmutableSet() }
|
||||
val toRemove = remember { baseNote.allReactionsByAuthor(accountViewModel.userProfile()).toImmutableSet() }
|
||||
|
||||
Popup(
|
||||
alignment = Alignment.BottomCenter,
|
||||
|
||||
+1
-1
@@ -623,7 +623,7 @@ open class CommentPostViewModel :
|
||||
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
||||
Amethyst.Companion.instance.okHttpClients
|
||||
.getHttpClient(
|
||||
accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url)
|
||||
accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url)
|
||||
?: false,
|
||||
)
|
||||
}
|
||||
|
||||
+20
-63
@@ -98,7 +98,6 @@ import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
@@ -266,13 +265,6 @@ class AccountViewModel(
|
||||
|
||||
fun userProfile(): User = account.userProfile()
|
||||
|
||||
suspend fun reactTo(
|
||||
note: Note,
|
||||
reaction: String,
|
||||
) {
|
||||
account.reactTo(note, reaction)
|
||||
}
|
||||
|
||||
fun <T : Event> observeByETag(
|
||||
kind: Int,
|
||||
eTag: HexKey,
|
||||
@@ -287,8 +279,8 @@ class AccountViewModel(
|
||||
note: Note,
|
||||
reaction: String,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val currentReactions = account.reactionTo(note, reaction)
|
||||
runIOCatching {
|
||||
val currentReactions = note.allReactionsOfContentByAuthor(userProfile(), reaction)
|
||||
if (currentReactions.isNotEmpty()) {
|
||||
account.delete(currentReactions)
|
||||
} else {
|
||||
@@ -298,14 +290,8 @@ class AccountViewModel(
|
||||
}
|
||||
|
||||
fun reactToOrDelete(note: Note) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val reaction = reactionChoices().first()
|
||||
if (hasReactedTo(note, reaction)) {
|
||||
deleteReactionTo(note, reaction)
|
||||
} else {
|
||||
reactTo(note, reaction)
|
||||
}
|
||||
}
|
||||
val reaction = reactionChoices().first()
|
||||
reactToOrDelete(note, reaction)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@@ -402,24 +388,6 @@ class AccountViewModel(
|
||||
noteMustShowExpandButtonFlows.put(note, it)
|
||||
}
|
||||
|
||||
fun hasReactedTo(
|
||||
baseNote: Note,
|
||||
reaction: String,
|
||||
): Boolean = account.hasReacted(baseNote, reaction)
|
||||
|
||||
suspend fun deleteReactionTo(
|
||||
note: Note,
|
||||
reaction: String,
|
||||
) {
|
||||
account.delete(account.reactionTo(note, reaction))
|
||||
}
|
||||
|
||||
fun hasBoosted(baseNote: Note): Boolean = account.hasBoosted(baseNote)
|
||||
|
||||
fun deleteBoostsTo(note: Note) {
|
||||
runIOCatching { account.delete(account.boostsTo(note)) }
|
||||
}
|
||||
|
||||
suspend fun calculateIfNoteWasZappedByAccount(
|
||||
zappedNote: Note,
|
||||
onWasZapped: (Boolean) -> Unit,
|
||||
@@ -977,7 +945,7 @@ class AccountViewModel(
|
||||
.verifyNip05(
|
||||
nip05,
|
||||
okHttpClient = {
|
||||
app.okHttpClients.getHttpClient(account.shouldUseTorForNIP05(it))
|
||||
app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForNIP05(it))
|
||||
},
|
||||
onSuccess = {
|
||||
// Marks user as verified
|
||||
@@ -1103,7 +1071,7 @@ class AccountViewModel(
|
||||
) {
|
||||
onResult(
|
||||
withContext(Dispatchers.Default) {
|
||||
LocalCache.findEarliestOtsForNote(note, account::otsResolver)
|
||||
LocalCache.findEarliestOtsForNote(note, account.otsResolverBuilder)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1228,21 +1196,7 @@ class AccountViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun createChatRoomFor(
|
||||
user: User,
|
||||
then: (ChatroomKey) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val withKey = ChatroomKey(persistentSetOf(user.pubkeyHex))
|
||||
account.chatroomList.createChatroom(withKey)
|
||||
then(withKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTorSettings(newTorSettings: TorSettings) =
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
account.settings.setTorSettings(newTorSettings)
|
||||
}
|
||||
fun setTorSettings(newTorSettings: TorSettings) = runIOCatching { account.settings.setTorSettings(newTorSettings) }
|
||||
|
||||
class Factory(
|
||||
val accountSettings: AccountSettings,
|
||||
@@ -1357,8 +1311,11 @@ class AccountViewModel(
|
||||
}
|
||||
|
||||
if (isWriteable()) {
|
||||
if (hasBoosted(baseNote)) {
|
||||
deleteBoostsTo(baseNote)
|
||||
val boosts = baseNote.boostedBy(userProfile())
|
||||
if (boosts.isNotEmpty()) {
|
||||
runIOCatching {
|
||||
account.delete(boosts)
|
||||
}
|
||||
} else {
|
||||
onMore()
|
||||
}
|
||||
@@ -1496,21 +1453,21 @@ class AccountViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun proxyPortFor(url: String): Int? = app.okHttpClients.getCurrentProxyPort(account.shouldUseTorForVideoDownload(url))
|
||||
fun proxyPortFor(url: String): Int? = app.okHttpClients.getCurrentProxyPort(account.privacyState.shouldUseTorForVideoDownload(url))
|
||||
|
||||
fun okHttpClientForNip96(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(url))
|
||||
fun okHttpClientForNip96(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(url))
|
||||
|
||||
fun okHttpClientForImage(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForImageDownload(url))
|
||||
fun okHttpClientForImage(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForImageDownload(url))
|
||||
|
||||
fun okHttpClientForVideo(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForVideoDownload(url))
|
||||
fun okHttpClientForVideo(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForVideoDownload(url))
|
||||
|
||||
fun okHttpClientForMoney(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForMoneyOperations(url))
|
||||
fun okHttpClientForMoney(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForMoneyOperations(url))
|
||||
|
||||
fun okHttpClientForPreview(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForPreviewUrl(url))
|
||||
fun okHttpClientForPreview(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForPreviewUrl(url))
|
||||
|
||||
fun okHttpClientForClean(url: NormalizedRelayUrl): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForClean(url))
|
||||
fun okHttpClientForClean(url: NormalizedRelayUrl): OkHttpClient = app.okHttpClients.getHttpClient(account.torRelayState.shouldUseTorForClean(url))
|
||||
|
||||
fun okHttpClientForTrustedRelays(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForTrustedRelays())
|
||||
fun okHttpClientForTrustedRelays(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForTrustedRelays())
|
||||
|
||||
fun dataSources() = app.sources
|
||||
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ class DecryptAndIndexProcessor(
|
||||
|
||||
is OtsEvent -> {
|
||||
// verifies new OTS upon arrival
|
||||
Amethyst.instance.otsVerifCache.cacheVerify(event, account::otsResolver)
|
||||
Amethyst.instance.otsVerifCache.cacheVerify(event, account.otsResolverBuilder)
|
||||
}
|
||||
|
||||
is DraftEvent -> {
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ fun SetProxyDeterminator(accountViewModel: AccountViewModel) {
|
||||
@Composable
|
||||
fun ObserveImageLoadingTor(accountViewModel: AccountViewModel) {
|
||||
LaunchedEffect(accountViewModel) {
|
||||
Amethyst.instance.setImageLoader(accountViewModel.account::shouldUseTorForImageDownload)
|
||||
Amethyst.instance.setImageLoader(accountViewModel.account.privacyState::shouldUseTorForImageDownload)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -640,7 +640,7 @@ class ChatNewMessageViewModel :
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
||||
Amethyst.instance.okHttpClients.getHttpClient(accountViewModel.account.shouldUseTorForImageDownload(item.link.url))
|
||||
Amethyst.instance.okHttpClients.getHttpClient(accountViewModel.account.privacyState.shouldUseTorForImageDownload(item.link.url))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -210,7 +210,7 @@ class ChannelMetadataViewModel : ViewModel() {
|
||||
alt = null,
|
||||
sensitiveContent = null,
|
||||
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||
onProgress = {},
|
||||
httpAuth = account::createHTTPAuthorization,
|
||||
context = context,
|
||||
@@ -223,7 +223,7 @@ class ChannelMetadataViewModel : ViewModel() {
|
||||
alt = null,
|
||||
sensitiveContent = null,
|
||||
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||
httpAuth = account::createBlossomUploadAuth,
|
||||
context = context,
|
||||
)
|
||||
|
||||
+1
-1
@@ -588,7 +588,7 @@ open class ChannelNewMessageViewModel :
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
iMetaAttachments.downloadAndPrepare(
|
||||
item.link.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -541,7 +541,7 @@ open class NewProductViewModel :
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
iMetaDescription.downloadAndPrepare(
|
||||
item.link.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -786,7 +786,7 @@ open class ShortNotePostViewModel :
|
||||
item.link.url,
|
||||
) {
|
||||
Amethyst.instance.okHttpClients.getHttpClient(
|
||||
accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url)
|
||||
accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url)
|
||||
?: false,
|
||||
)
|
||||
}
|
||||
|
||||
+6
-6
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults.contentPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -36,6 +37,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
|
||||
@Composable
|
||||
fun MessageButton(
|
||||
@@ -49,12 +51,10 @@ fun MessageButton(
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = {
|
||||
accountViewModel.createChatRoomFor(user) { chatroomKey ->
|
||||
nav.nav(
|
||||
routeToMessage(
|
||||
room = chatroomKey,
|
||||
account = accountViewModel.account,
|
||||
),
|
||||
nav.nav {
|
||||
routeToMessage(
|
||||
room = ChatroomKey(setOf(user.pubkeyHex)),
|
||||
account = accountViewModel.account,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ abstract class BasicRelaySetupInfoModel : ViewModel() {
|
||||
Nip11CachedRetriever.loadRelayInfo(
|
||||
relay = item.relay,
|
||||
okHttpClient = {
|
||||
Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForClean(item.relay))
|
||||
Amethyst.instance.okHttpClients.getHttpClient(account.torRelayState.shouldUseTorForClean(item.relay))
|
||||
},
|
||||
onInfo = {
|
||||
togglePaidRelay(item, it.limitation?.payment_required ?: false)
|
||||
|
||||
+2
-2
@@ -93,7 +93,7 @@ class Nip65RelayListViewModel : ViewModel() {
|
||||
_homeRelays.value.forEach { item ->
|
||||
Nip11CachedRetriever.loadRelayInfo(
|
||||
relay = item.relay,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForClean(item.relay)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.torRelayState.shouldUseTorForClean(item.relay)) },
|
||||
onInfo = {
|
||||
toggleHomePaidRelay(item, it.limitation?.payment_required ?: false)
|
||||
},
|
||||
@@ -104,7 +104,7 @@ class Nip65RelayListViewModel : ViewModel() {
|
||||
_notificationRelays.value.forEach { item ->
|
||||
Nip11CachedRetriever.loadRelayInfo(
|
||||
relay = item.relay,
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForClean(item.relay)) },
|
||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.torRelayState.shouldUseTorForClean(item.relay)) },
|
||||
onInfo = {
|
||||
toggleNotifPaidRelay(item, it.limitation?.payment_required ?: false)
|
||||
},
|
||||
|
||||
@@ -92,7 +92,7 @@ class PrivateZapTests {
|
||||
val privateZapRequest =
|
||||
runBlocking {
|
||||
LnZapRequestEvent.create(
|
||||
originalNote = poll,
|
||||
zappedEvent = poll,
|
||||
relays = setOf("wss://relay.damus.io/"),
|
||||
signer = loggedIn,
|
||||
pollOption = 0,
|
||||
@@ -152,7 +152,7 @@ class PrivateZapTests {
|
||||
val privateZapRequest =
|
||||
runBlocking {
|
||||
LnZapRequestEvent.create(
|
||||
originalNote = textNote,
|
||||
zappedEvent = textNote,
|
||||
relays = setOf("wss://relay.damus.io/", "wss://relay.damus2.io/", "wss://relay.damus3.io/"),
|
||||
signer = loggedIn,
|
||||
pollOption = null,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip03Timestamp
|
||||
|
||||
interface OtsResolverBuilder {
|
||||
fun build(): OtsResolver
|
||||
}
|
||||
+4
-4
@@ -29,15 +29,15 @@ class VerificationStateCache {
|
||||
|
||||
fun verify(
|
||||
event: OtsEvent,
|
||||
resolverBuilder: () -> OtsResolver,
|
||||
resolverBuilder: OtsResolverBuilder,
|
||||
): VerificationState {
|
||||
cache.put(event.id, VerificationState.Verifying)
|
||||
return event.verifyState(resolverBuilder()).also { cache.put(event.id, it) }
|
||||
return event.verifyState(resolverBuilder.build()).also { cache.put(event.id, it) }
|
||||
}
|
||||
|
||||
fun cacheVerify(
|
||||
event: OtsEvent,
|
||||
resolverBuilder: () -> OtsResolver,
|
||||
resolverBuilder: OtsResolverBuilder,
|
||||
): VerificationState =
|
||||
when (val verif = cache[event.id]) {
|
||||
is VerificationState.Verifying -> verif
|
||||
@@ -45,7 +45,7 @@ class VerificationStateCache {
|
||||
is VerificationState.NetworkError -> {
|
||||
// try again in 5 mins
|
||||
if (verif.time < TimeUtils.fiveMinutesAgo()) {
|
||||
event.verifyState(resolverBuilder()).also { cache.put(event.id, it) }
|
||||
event.verifyState(resolverBuilder.build()).also { cache.put(event.id, it) }
|
||||
} else {
|
||||
verify(event, resolverBuilder)
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ class LnZapRequestEvent(
|
||||
const val ALT = "Zap request"
|
||||
|
||||
suspend fun create(
|
||||
originalNote: Event,
|
||||
relays: Set<String>,
|
||||
zappedEvent: Event,
|
||||
relays: Set<NormalizedRelayUrl>,
|
||||
signer: NostrSigner,
|
||||
pollOption: Int?,
|
||||
message: String,
|
||||
@@ -98,13 +98,13 @@ class LnZapRequestEvent(
|
||||
): LnZapRequestEvent {
|
||||
var tags =
|
||||
listOf(
|
||||
arrayOf("e", originalNote.id),
|
||||
arrayOf("p", toUserPubHex ?: originalNote.pubKey),
|
||||
arrayOf("relays") + relays,
|
||||
arrayOf("e", zappedEvent.id),
|
||||
arrayOf("p", toUserPubHex ?: zappedEvent.pubKey),
|
||||
arrayOf("relays") + relays.map { it.url },
|
||||
AltTag.assemble(ALT),
|
||||
)
|
||||
if (originalNote is AddressableEvent) {
|
||||
tags = tags + listOf(arrayOf("a", originalNote.aTag().toTag()))
|
||||
if (zappedEvent is AddressableEvent) {
|
||||
tags = tags + listOf(arrayOf("a", zappedEvent.aTag().toTag()))
|
||||
}
|
||||
if (pollOption != null && pollOption >= 0) {
|
||||
tags = tags + listOf(arrayOf(PollOptionTag.TAG_NAME, pollOption.toString()))
|
||||
|
||||
Reference in New Issue
Block a user