Adds the ability to see and reply to Git Issues and Patches.
This commit is contained in:
@@ -67,6 +67,7 @@ import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
|||||||
import com.vitorpamplona.quartz.events.GeneralListEvent
|
import com.vitorpamplona.quartz.events.GeneralListEvent
|
||||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||||
|
import com.vitorpamplona.quartz.events.GitReplyEvent
|
||||||
import com.vitorpamplona.quartz.events.HTTPAuthorizationEvent
|
import com.vitorpamplona.quartz.events.HTTPAuthorizationEvent
|
||||||
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
|
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
|
||||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||||
@@ -1349,6 +1350,63 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sendGitReply(
|
||||||
|
message: String,
|
||||||
|
replyTo: List<Note>?,
|
||||||
|
mentions: List<User>?,
|
||||||
|
repository: ATag?,
|
||||||
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
|
wantsToMarkAsSensitive: Boolean,
|
||||||
|
zapRaiserAmount: Long? = null,
|
||||||
|
replyingTo: String?,
|
||||||
|
root: String?,
|
||||||
|
directMentions: Set<HexKey>,
|
||||||
|
forkedFrom: Event?,
|
||||||
|
relayList: List<Relay>? = null,
|
||||||
|
geohash: String? = null,
|
||||||
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
) {
|
||||||
|
if (!isWriteable()) return
|
||||||
|
|
||||||
|
val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex }
|
||||||
|
val mentionsHex = mentions?.map { it.pubkeyHex }
|
||||||
|
val addresses = listOfNotNull(repository) + (replyTo?.mapNotNull { it.address() } ?: emptyList())
|
||||||
|
|
||||||
|
GitReplyEvent.create(
|
||||||
|
msg = message,
|
||||||
|
replyTos = repliesToHex,
|
||||||
|
mentions = mentionsHex,
|
||||||
|
addresses = addresses,
|
||||||
|
extraTags = null,
|
||||||
|
zapReceiver = zapReceiver,
|
||||||
|
markAsSensitive = wantsToMarkAsSensitive,
|
||||||
|
zapRaiserAmount = zapRaiserAmount,
|
||||||
|
replyingTo = replyingTo,
|
||||||
|
root = root,
|
||||||
|
directMentions = directMentions,
|
||||||
|
geohash = geohash,
|
||||||
|
nip94attachments = nip94attachments,
|
||||||
|
forkedFrom = forkedFrom,
|
||||||
|
signer = signer,
|
||||||
|
) {
|
||||||
|
Client.send(it, relayList = relayList)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
|
||||||
|
// broadcast replied notes
|
||||||
|
replyingTo?.let {
|
||||||
|
LocalCache.getNoteIfExists(replyingTo)?.event?.let {
|
||||||
|
Client.send(it, relayList = relayList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
|
||||||
|
addresses?.forEach {
|
||||||
|
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
|
||||||
|
Client.send(it, relayList = relayList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun sendPost(
|
fun sendPost(
|
||||||
message: String,
|
message: String,
|
||||||
replyTo: List<Note>?,
|
replyTo: List<Note>?,
|
||||||
|
|||||||
@@ -451,14 +451,21 @@ object LocalCache {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val repository = event.repository()?.toTag()
|
||||||
|
|
||||||
val replyTo =
|
val replyTo =
|
||||||
event
|
event
|
||||||
.tagsWithoutCitations()
|
.tagsWithoutCitations()
|
||||||
.filter { it != event.repository()?.toTag() }
|
.filter { it != repository }
|
||||||
.mapNotNull { checkGetOrCreateNote(it) }
|
.mapNotNull { checkGetOrCreateNote(it) }
|
||||||
|
|
||||||
|
// println("New GitReply ${event.id} for ${replyTo.firstOrNull()?.event?.id()} ${event.tagsWithoutCitations().filter { it != event.repository()?.toTag() }.firstOrNull()}")
|
||||||
|
|
||||||
note.loadEvent(event, author, replyTo)
|
note.loadEvent(event, author, replyTo)
|
||||||
|
|
||||||
|
// Counts the replies
|
||||||
|
replyTo.forEach { it.addReply(note) }
|
||||||
|
|
||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
|||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
||||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||||
|
import com.vitorpamplona.quartz.events.GitReplyEvent
|
||||||
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
|
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
|
||||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||||
import com.vitorpamplona.quartz.events.OtsEvent
|
import com.vitorpamplona.quartz.events.OtsEvent
|
||||||
@@ -138,6 +139,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
|||||||
PollNoteEvent.KIND,
|
PollNoteEvent.KIND,
|
||||||
OtsEvent.KIND,
|
OtsEvent.KIND,
|
||||||
TextNoteModificationEvent.KIND,
|
TextNoteModificationEvent.KIND,
|
||||||
|
GitReplyEvent.KIND,
|
||||||
),
|
),
|
||||||
tags = mapOf("e" to it.map { it.idHex }),
|
tags = mapOf("e" to it.map { it.idHex }),
|
||||||
since = findMinimumEOSEs(it),
|
since = findMinimumEOSEs(it),
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import com.vitorpamplona.quartz.events.Event
|
|||||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageEvent
|
import com.vitorpamplona.quartz.events.FileStorageEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
||||||
|
import com.vitorpamplona.quartz.events.GitIssueEvent
|
||||||
import com.vitorpamplona.quartz.events.Price
|
import com.vitorpamplona.quartz.events.Price
|
||||||
import com.vitorpamplona.quartz.events.PrivateDmEvent
|
import com.vitorpamplona.quartz.events.PrivateDmEvent
|
||||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||||
@@ -434,6 +435,45 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (originalNote?.event is GitIssueEvent) {
|
||||||
|
val originalNoteEvent = originalNote?.event as GitIssueEvent
|
||||||
|
// adds markers
|
||||||
|
val rootId =
|
||||||
|
originalNoteEvent.rootIssueOrPatch() // if it has a marker as root
|
||||||
|
?: originalNote
|
||||||
|
?.replyTo
|
||||||
|
?.firstOrNull { it.event != null && it.replyTo?.isEmpty() == true }
|
||||||
|
?.idHex // if it has loaded events with zero replies in the reply list
|
||||||
|
?: originalNote?.replyTo?.firstOrNull()?.idHex // old rules, first item is root.
|
||||||
|
?: originalNote?.idHex
|
||||||
|
|
||||||
|
val replyId = originalNote?.idHex
|
||||||
|
|
||||||
|
val replyToSet =
|
||||||
|
if (forkedFromNote != null) {
|
||||||
|
(listOfNotNull(forkedFromNote) + (tagger.eTags ?: emptyList())).ifEmpty { null }
|
||||||
|
} else {
|
||||||
|
tagger.eTags
|
||||||
|
}
|
||||||
|
|
||||||
|
val repositoryAddress = originalNoteEvent.repository()
|
||||||
|
|
||||||
|
account?.sendGitReply(
|
||||||
|
message = tagger.message,
|
||||||
|
replyTo = replyToSet,
|
||||||
|
mentions = tagger.pTags,
|
||||||
|
repository = repositoryAddress,
|
||||||
|
zapReceiver = zapReceiver,
|
||||||
|
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
|
||||||
|
zapRaiserAmount = localZapRaiserAmount,
|
||||||
|
replyingTo = replyId,
|
||||||
|
root = rootId,
|
||||||
|
directMentions = tagger.directMentions,
|
||||||
|
forkedFrom = forkedFromNote?.event as? Event,
|
||||||
|
relayList = relayList,
|
||||||
|
geohash = geoHash,
|
||||||
|
nip94attachments = usedAttachments,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
if (wantsPoll) {
|
if (wantsPoll) {
|
||||||
account?.sendPoll(
|
account?.sendPoll(
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class GitIssueEvent(
|
|||||||
|
|
||||||
private fun repositoryHex() = innerRepository()?.getOrNull(1)
|
private fun repositoryHex() = innerRepository()?.getOrNull(1)
|
||||||
|
|
||||||
fun rootIssueOrPath() = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1)
|
fun rootIssueOrPatch() = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1)
|
||||||
|
|
||||||
fun repository() =
|
fun repository() =
|
||||||
innerRepository()?.let {
|
innerRepository()?.let {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.events
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.quartz.encoders.ATag
|
import com.vitorpamplona.quartz.encoders.ATag
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
|
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments
|
||||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
@@ -75,5 +76,87 @@ class GitReplyEvent(
|
|||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
msg: String,
|
||||||
|
replyTos: List<String>? = null,
|
||||||
|
mentions: List<String>? = null,
|
||||||
|
addresses: List<ATag>? = null,
|
||||||
|
extraTags: List<String>? = null,
|
||||||
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
|
markAsSensitive: Boolean = false,
|
||||||
|
zapRaiserAmount: Long? = null,
|
||||||
|
replyingTo: String? = null,
|
||||||
|
root: String? = null,
|
||||||
|
directMentions: Set<HexKey> = emptySet(),
|
||||||
|
geohash: String? = null,
|
||||||
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
forkedFrom: Event? = null,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (GitReplyEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val tags = mutableListOf<Array<String>>()
|
||||||
|
replyTos?.let {
|
||||||
|
tags.addAll(
|
||||||
|
it.positionalMarkedTags(
|
||||||
|
tagName = "e",
|
||||||
|
root = root,
|
||||||
|
replyingTo = replyingTo,
|
||||||
|
directMentions = directMentions,
|
||||||
|
forkedFrom = forkedFrom?.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mentions?.forEach {
|
||||||
|
if (it in directMentions) {
|
||||||
|
tags.add(arrayOf("p", it, "", "mention"))
|
||||||
|
} else {
|
||||||
|
tags.add(arrayOf("p", it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replyTos?.forEach {
|
||||||
|
if (it in directMentions) {
|
||||||
|
tags.add(arrayOf("q", it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addresses
|
||||||
|
?.map { it.toTag() }
|
||||||
|
?.let {
|
||||||
|
tags.addAll(
|
||||||
|
it.positionalMarkedTags(
|
||||||
|
tagName = "a",
|
||||||
|
root = root,
|
||||||
|
replyingTo = replyingTo,
|
||||||
|
directMentions = directMentions,
|
||||||
|
forkedFrom = (forkedFrom as? AddressableEvent)?.address()?.toTag(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
findHashtags(msg).forEach {
|
||||||
|
tags.add(arrayOf("t", it))
|
||||||
|
tags.add(arrayOf("t", it.lowercase()))
|
||||||
|
}
|
||||||
|
extraTags?.forEach { tags.add(arrayOf("t", it)) }
|
||||||
|
zapReceiver?.forEach {
|
||||||
|
tags.add(arrayOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||||
|
}
|
||||||
|
findURLs(msg).forEach { tags.add(arrayOf("r", it)) }
|
||||||
|
if (markAsSensitive) {
|
||||||
|
tags.add(arrayOf("content-warning", ""))
|
||||||
|
}
|
||||||
|
zapRaiserAmount?.let { tags.add(arrayOf("zapraiser", "$it")) }
|
||||||
|
geohash?.let { tags.addAll(geohashMipMap(it)) }
|
||||||
|
nip94attachments?.let {
|
||||||
|
it.forEach {
|
||||||
|
Nip92MediaAttachments().convertFromFileHeader(it)?.let {
|
||||||
|
tags.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tags.add(arrayOf("alt", "a git issue reply"))
|
||||||
|
|
||||||
|
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,45 +123,45 @@ class TextNoteEvent(
|
|||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of NIP-10 marked tags that are also ordered at best effort to support the
|
|
||||||
* deprecated method of positional tags to maximize backwards compatibility with clients that
|
|
||||||
* support replies but have not been updated to understand tag markers.
|
|
||||||
*
|
|
||||||
* https://github.com/nostr-protocol/nips/blob/master/10.md
|
|
||||||
*
|
|
||||||
* The tag to the root of the reply chain goes first. The tag to the reply event being responded
|
|
||||||
* to goes last. The order for any other tag does not matter, so keep the relative order.
|
|
||||||
*/
|
|
||||||
private fun List<String>.positionalMarkedTags(
|
|
||||||
tagName: String,
|
|
||||||
root: String?,
|
|
||||||
replyingTo: String?,
|
|
||||||
directMentions: Set<HexKey>,
|
|
||||||
forkedFrom: String?,
|
|
||||||
) = sortedWith { o1, o2 ->
|
|
||||||
when {
|
|
||||||
o1 == o2 -> 0
|
|
||||||
o1 == root -> -1 // root goes first
|
|
||||||
o2 == root -> 1 // root goes first
|
|
||||||
o1 == replyingTo -> 1 // reply event being responded to goes last
|
|
||||||
o2 == replyingTo -> -1 // reply event being responded to goes last
|
|
||||||
else -> 0 // keep the relative order for any other tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map {
|
|
||||||
when (it) {
|
|
||||||
root -> arrayOf(tagName, it, "", "root")
|
|
||||||
replyingTo -> arrayOf(tagName, it, "", "reply")
|
|
||||||
forkedFrom -> arrayOf(tagName, it, "", "fork")
|
|
||||||
in directMentions -> arrayOf(tagName, it, "", "mention")
|
|
||||||
else -> arrayOf(tagName, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findURLs(text: String): List<String> {
|
fun findURLs(text: String): List<String> {
|
||||||
return UrlDetector(text, UrlDetectorOptions.Default).detect().map { it.originalUrl }
|
return UrlDetector(text, UrlDetectorOptions.Default).detect().map { it.originalUrl }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of NIP-10 marked tags that are also ordered at best effort to support the
|
||||||
|
* deprecated method of positional tags to maximize backwards compatibility with clients that
|
||||||
|
* support replies but have not been updated to understand tag markers.
|
||||||
|
*
|
||||||
|
* https://github.com/nostr-protocol/nips/blob/master/10.md
|
||||||
|
*
|
||||||
|
* The tag to the root of the reply chain goes first. The tag to the reply event being responded
|
||||||
|
* to goes last. The order for any other tag does not matter, so keep the relative order.
|
||||||
|
*/
|
||||||
|
fun List<String>.positionalMarkedTags(
|
||||||
|
tagName: String,
|
||||||
|
root: String?,
|
||||||
|
replyingTo: String?,
|
||||||
|
directMentions: Set<HexKey>,
|
||||||
|
forkedFrom: String?,
|
||||||
|
) = sortedWith { o1, o2 ->
|
||||||
|
when {
|
||||||
|
o1 == o2 -> 0
|
||||||
|
o1 == root -> -1 // root goes first
|
||||||
|
o2 == root -> 1 // root goes first
|
||||||
|
o1 == replyingTo -> 1 // reply event being responded to goes last
|
||||||
|
o2 == replyingTo -> -1 // reply event being responded to goes last
|
||||||
|
else -> 0 // keep the relative order for any other tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
when (it) {
|
||||||
|
root -> arrayOf(tagName, it, "", "root")
|
||||||
|
replyingTo -> arrayOf(tagName, it, "", "reply")
|
||||||
|
forkedFrom -> arrayOf(tagName, it, "", "fork")
|
||||||
|
in directMentions -> arrayOf(tagName, it, "", "mention")
|
||||||
|
else -> arrayOf(tagName, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user