Add cross-posting

This commit is contained in:
Jon Staab 2023-11-21 08:52:28 -08:00
parent 5e3c919245
commit 54d738aefa
5 changed files with 131 additions and 45 deletions

1
.env
View File

@ -4,6 +4,7 @@ VITE_DEFAULT_RELAYS=wss://purplepag.es,wss://relay.damus.io,wss://relay.nostr.ba
VITE_DEFAULT_FOLLOWS=1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,cc8d072efdcc676fcbac14f6cd6825edc3576e55eb786a2a975ee034a6a026cb,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075,3335d373e6c1b5bc669b4b1220c08728ea8ce622e5a7cfeeb4c0001d91ded1de,0b118e40d6f3dfabb17f21a94a647701f140d8b063a9e84fe6e483644edc09cb,b83a28b7e4e5d20bd960c5faeb6625f95529166b8bdb045d42634a2f35919450,958b754a1d3de5b5eca0fe31d2d555f451325f8498a83da1997b7fcd5c39e88c,b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc,b708f7392f588406212c3882e7b3bc0d9b08d62f95fa170d099127ece2770e5e,5c508c34f58866ec7341aaf10cc1af52e9232bb9f859c8103ca5ecf2aa93bf78,baf27a4cc4da49913e7fdecc951fd3b971c9279959af62b02b761a043c33384c,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,0fecf65daa26faf3f668e8143325a4c199a040b6345ed40a08614d7dd85b1823,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,f783ba3b12b91e375aba6594015b90bd95f7e132b03cc8c4c52ce0a7c36aab52,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,a1fc5dfd7ffcf563c89155b466751b580d115e136e2f8c90e8913385bbedb1cf,84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240 VITE_DEFAULT_FOLLOWS=1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,cc8d072efdcc676fcbac14f6cd6825edc3576e55eb786a2a975ee034a6a026cb,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075,3335d373e6c1b5bc669b4b1220c08728ea8ce622e5a7cfeeb4c0001d91ded1de,0b118e40d6f3dfabb17f21a94a647701f140d8b063a9e84fe6e483644edc09cb,b83a28b7e4e5d20bd960c5faeb6625f95529166b8bdb045d42634a2f35919450,958b754a1d3de5b5eca0fe31d2d555f451325f8498a83da1997b7fcd5c39e88c,b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc,b708f7392f588406212c3882e7b3bc0d9b08d62f95fa170d099127ece2770e5e,5c508c34f58866ec7341aaf10cc1af52e9232bb9f859c8103ca5ecf2aa93bf78,baf27a4cc4da49913e7fdecc951fd3b971c9279959af62b02b761a043c33384c,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,0fecf65daa26faf3f668e8143325a4c199a040b6345ed40a08614d7dd85b1823,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,f783ba3b12b91e375aba6594015b90bd95f7e132b03cc8c4c52ce0a7c36aab52,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,a1fc5dfd7ffcf563c89155b466751b580d115e136e2f8c90e8913385bbedb1cf,84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240
VITE_IMGPROXY_URL=https://imgproxy.coracle.social VITE_IMGPROXY_URL=https://imgproxy.coracle.social
VITE_DUFFLEPUD_URL=https://dufflepud.onrender.com VITE_DUFFLEPUD_URL=https://dufflepud.onrender.com
VITE_ENABLE_GROUPS=true
VITE_ENABLE_ZAPS=true VITE_ENABLE_ZAPS=true
VITE_FORCE_RELAYS= VITE_FORCE_RELAYS=
VITE_LOGO_URL= VITE_LOGO_URL=

View File

@ -154,6 +154,9 @@
zapper zapper
) )
// Split out reposts
$: reposts = children.filter(e => [6, 16].includes(e.kind))
onMount(async () => { onMount(async () => {
const zapAddress = Tags.from(note).getValue("zap") const zapAddress = Tags.from(note).getValue("zap")
@ -191,6 +194,11 @@
kinds.push(9735) kinds.push(9735)
} }
if ($env.ENABLE_GROUPS && !event.wrap) {
kinds.push(6)
kinds.push(16)
}
load({ load({
relays: mergeHints([relays, getReplyHints(event)]), relays: mergeHints([relays, getReplyHints(event)]),
filters: getReplyFilters([event], {kinds}), filters: getReplyFilters([event], {kinds}),

View File

@ -3,12 +3,20 @@
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import {toNostrURI, createEvent} from "paravel" import {toNostrURI, createEvent} from "paravel"
import {tweened} from "svelte/motion" import {tweened} from "svelte/motion"
import {identity, sum, pluck, sortBy} from "ramda" import {identity, sum, uniqBy, prop, pluck, sortBy} from "ramda"
import {formatSats} from "src/util/misc" import {formatSats} from "src/util/misc"
import {LOCAL_RELAY_URL, getGroupAddress, asNostrEvent, getIdOrAddress} from "src/util/nostr" import {
LOCAL_RELAY_URL,
getGroupAddress,
getIdOrAddressTag,
asNostrEvent,
getIdOrAddress,
} from "src/util/nostr"
import {quantify} from "hurdak" import {quantify} from "hurdak"
import {toast} from "src/partials/state" import {toast} from "src/partials/state"
import Popover from "src/partials/Popover.svelte" import Popover from "src/partials/Popover.svelte"
import Card from "src/partials/Card.svelte"
import Heading from "src/partials/Heading.svelte"
import ColorDot from "src/partials/ColorDot.svelte" import ColorDot from "src/partials/ColorDot.svelte"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
import Modal from "src/partials/Modal.svelte" import Modal from "src/partials/Modal.svelte"
@ -16,15 +24,18 @@
import CopyValue from "src/partials/CopyValue.svelte" import CopyValue from "src/partials/CopyValue.svelte"
import PersonBadge from "src/app/shared/PersonBadge.svelte" import PersonBadge from "src/app/shared/PersonBadge.svelte"
import RelayCard from "src/app/shared/RelayCard.svelte" import RelayCard from "src/app/shared/RelayCard.svelte"
import GroupSummary from "src/app/shared/GroupSummary.svelte"
import {router} from "src/app/router" import {router} from "src/app/router"
import type {Event} from "src/engine" import type {Event} from "src/engine"
import { import {
env, env,
mute, mute,
unmute, unmute,
groups,
canSign, canSign,
session, session,
Publisher, Publisher,
mention,
signer, signer,
deriveGroupAccess, deriveGroupAccess,
publishToZeroOrMoreGroups, publishToZeroOrMoreGroups,
@ -44,11 +55,10 @@
export let showMuted export let showMuted
export let showEntire export let showEntire
export let removeFromContext export let removeFromContext
export let replies export let replies, likes, zaps
export let likes
export let zaps
export let zapper export let zapper
const address = getGroupAddress(note)
const relays = getEventHints(note) const relays = getEventHints(note)
const nevent = nip19.neventEncode({id: note.id, relays}) const nevent = nip19.neventEncode({id: note.id, relays})
const muted = isEventMuted.derived($isEventMuted => $isEventMuted(note, true)) const muted = isEventMuted.derived($isEventMuted => $isEventMuted(note, true))
@ -59,6 +69,10 @@
//const report = () => router.at("notes").of(note.id, {relays: getEventHints(note)}).at('report').qp({pubkey: note.pubkey}).open() //const report = () => router.at("notes").of(note.id, {relays: getEventHints(note)}).at('report').qp({pubkey: note.pubkey}).open()
const setView = v => {
view = v
}
const label = () => router.at("notes").of(note.id, {relays}).at("label").open() const label = () => router.at("notes").of(note.id, {relays}).at("label").open()
const quote = () => router.at("notes/create").cx({quote: note, relays}).open() const quote = () => router.at("notes/create").cx({quote: note, relays}).open()
@ -87,6 +101,24 @@
removeFromContext(e) removeFromContext(e)
} }
const crossPost = async address => {
const relays = getPublishHints(note)
const tags = [getIdOrAddressTag(note, relays[0]), mention(note.pubkey)]
const content = JSON.stringify(asNostrEvent(note))
const shouldWrap = groups.key(address).get()?.access === "closed"
let template
if (note.kind === 1) {
template = createEvent(6, {content, tags})
} else {
template = createEvent(16, {content, tags: [...tags, ["k", note.kind]]})
}
publishToZeroOrMoreGroups([address], template, {relays, shouldWrap})
setView(null)
}
const startZap = () => const startZap = () =>
router router
.at("people") .at("people")
@ -110,11 +142,24 @@
.cx({relays: [url]}) .cx({relays: [url]})
.open() .open()
let like, allLikes, zap const groupOptions = session.derived($session => {
let showDetails = false const options = []
for (const addr of Object.keys($session.groups || {})) {
const group = groups.key(addr).get()
const access = deriveGroupAccess(addr).get()
if (group && access === "granted" && addr !== address) {
options.push(group)
}
}
return uniqBy(prop("address"), options)
})
let view
let actions = [] let actions = []
$: address = getGroupAddress(note)
$: disableActions = $: disableActions =
!$canSign || !$canSign ||
($muted && !showMuted) || ($muted && !showMuted) ||
@ -140,6 +185,7 @@
actions = [] actions = []
actions.push({label: "Quote", icon: "quote-left", onClick: quote}) actions.push({label: "Quote", icon: "quote-left", onClick: quote})
actions.push({label: "Cross-post", icon: "shuffle", onClick: () => setView('cross-post')})
actions.push({label: "Tag", icon: "tag", onClick: label}) actions.push({label: "Tag", icon: "tag", onClick: label})
//actions.push({label: "Report", icon: "triangle-exclamation", onClick: report}) //actions.push({label: "Report", icon: "triangle-exclamation", onClick: report})
@ -156,9 +202,7 @@
actions.push({ actions.push({
label: "Details", label: "Details",
icon: "info", icon: "info",
onClick: () => { onClick: () => setView("info"),
showDetails = true
},
}) })
} }
</script> </script>
@ -243,12 +287,10 @@
</div> </div>
</div> </div>
{#if showDetails} {#if view}
<Modal <Modal onEscape={() => setView(null)}>
onEscape={() => {
showDetails = false
}}>
<Content> <Content>
{#if view === "info"}
{#if zaps.length > 0} {#if zaps.length > 0}
<h1 class="staatliches text-2xl">Zapped By</h1> <h1 class="staatliches text-2xl">Zapped By</h1>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
@ -269,6 +311,7 @@
{/each} {/each}
</div> </div>
{/if} {/if}
{#if note.seen_on.length > 0}
<h1 class="staatliches text-2xl">Relays</h1> <h1 class="staatliches text-2xl">Relays</h1>
<p>This note was found on {quantify(note.seen_on.length, "relay")} below.</p> <p>This note was found on {quantify(note.seen_on.length, "relay")} below.</p>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
@ -276,10 +319,35 @@
<RelayCard relay={{url}} /> <RelayCard relay={{url}} />
{/each} {/each}
</div> </div>
{/if}
<h1 class="staatliches text-2xl">Details</h1> <h1 class="staatliches text-2xl">Details</h1>
<CopyValue label="Link" value={toNostrURI(nevent)} /> <CopyValue label="Link" value={toNostrURI(nevent)} />
<CopyValue label="Event ID" encode={nip19.noteEncode} value={note.id} /> <CopyValue label="Event ID" encode={nip19.noteEncode} value={note.id} />
<CopyValue label="Event JSON" value={JSON.stringify(asNostrEvent(note))} /> <CopyValue label="Event JSON" value={JSON.stringify(asNostrEvent(note))} />
{:else if view === "cross-post"}
<div class="mb-4 flex items-center justify-center">
<Heading>Cross-post</Heading>
</div>
<div>Select where you'd like to post to:</div>
<div class="flex flex-col gap-2">
{#if address}
<Card invertColors interactive on:click={() => crossPost()}>
<div class="flex gap-4 text-gray-1">
<i class="fa fa-earth-asia fa-2x" />
<div class="flex min-w-0 flex-grow flex-col gap-4">
<p class="text-2xl">Global</p>
<p>Post to your main feed.</p>
</div>
</div>
</Card>
{/if}
{#each $groupOptions as g (g.address)}
<Card invertColors interactive on:click={() => crossPost(g.address)}>
<GroupSummary address={g.address} />
</Card>
{/each}
</div>
{/if}
</Content> </Content>
</Modal> </Modal>
{/if} {/if}

View File

@ -46,8 +46,7 @@ const MULTIPLEXTR_URL = import.meta.env.VITE_MULTIPLEXTR_URL
const FORCE_RELAYS = fromCsv(import.meta.env.VITE_FORCE_RELAYS) const FORCE_RELAYS = fromCsv(import.meta.env.VITE_FORCE_RELAYS)
const DVM_RELAYS = const DVM_RELAYS = FORCE_RELAYS.length > 0 ? FORCE_RELAYS : fromCsv(import.meta.env.VITE_DVM_RELAYS)
FORCE_RELAYS.length > 0 ? FORCE_RELAYS : fromCsv(import.meta.env.VITE_DVM_RELAYS)
const SEARCH_RELAYS = const SEARCH_RELAYS =
FORCE_RELAYS.length > 0 ? FORCE_RELAYS : ["wss://relay.nostr.band", "wss://nostr.wine"] FORCE_RELAYS.length > 0 ? FORCE_RELAYS : ["wss://relay.nostr.band", "wss://nostr.wine"]
@ -59,6 +58,8 @@ const DEFAULT_FOLLOWS = fromCsv(import.meta.env.VITE_DEFAULT_FOLLOWS)
const ENABLE_ZAPS = JSON.parse(import.meta.env.VITE_ENABLE_ZAPS) const ENABLE_ZAPS = JSON.parse(import.meta.env.VITE_ENABLE_ZAPS)
const ENABLE_GROUPS = JSON.parse(import.meta.env.VITE_ENABLE_GROUPS)
// Prep our env // Prep our env
env.set({ env.set({
DEFAULT_FOLLOWS, DEFAULT_FOLLOWS,
@ -70,6 +71,7 @@ env.set({
SEARCH_RELAYS, SEARCH_RELAYS,
DEFAULT_RELAYS, DEFAULT_RELAYS,
ENABLE_ZAPS, ENABLE_ZAPS,
ENABLE_GROUPS,
}) })
// Throw some hardcoded defaults in there // Throw some hardcoded defaults in there

View File

@ -62,6 +62,13 @@ export const getIdOrAddress = e => {
return e.id return e.id
} }
export const getIdOrAddressTag = (e, hint) => {
const value = getIdOrAddress(e)
const type = value.includes(":") ? "a" : "e"
return [type, value, hint]
}
export const getGroupAddress = e => export const getGroupAddress = e =>
Tags.from(e) Tags.from(e)
.type("a") .type("a")