mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Remove nip28 chat
This commit is contained in:
parent
c4541e2fa6
commit
95a24abd0f
@ -118,4 +118,5 @@ An important goal for the UX is speed without reflow due to late-arriving events
|
|||||||
|
|
||||||
I'm still working through the privacy tradeoff. In general, I opt for the public version of any primitive (follows, relays list) so that more sophisticated functionality can be built on top of open social graphs. However, in the case of application-specific settings and usage data, there is no benefit to sharing that information publicly, and so it is encrypted. One of the main goals of Coracle is also to eventually support private groups alongside public notes, which will present a tricky design challenge.
|
I'm still working through the privacy tradeoff. In general, I opt for the public version of any primitive (follows, relays list) so that more sophisticated functionality can be built on top of open social graphs. However, in the case of application-specific settings and usage data, there is no benefit to sharing that information publicly, and so it is encrypted. One of the main goals of Coracle is also to eventually support private groups alongside public notes, which will present a tricky design challenge.
|
||||||
|
|
||||||
Coracle only has support for DMs and chat because it was added early on and people use it. These are not the priority of the project, and hopefully can be replaced with an embedded micro-app at some point in the future. Insofar as certain NIPs can augment regular social content (public and private) however, they should be supported. This includes things like Data Vending Machines and Client recommendations. Rendering support is included for long-form posts, profile updates, and more, and advanced search and custom feeds are an area of research I'm interested in pushing forward.
|
Coracle only has support for DMs because it was added early on and people use it. These are not the priority of the project, and hopefully can be replaced with an embedded micro-app at some point in the future. Insofar as certain NIPs can augment regular social content (public and private) however, they should be supported. This includes things like Data Vending Machines and Client recommendations. Rendering support is included for long-form posts, profile updates, and more, and advanced search and custom feeds are an area of research I'm interested in pushing forward.
|
||||||
|
|
||||||
|
28
README.md
28
README.md
@ -10,28 +10,36 @@ If you like Coracle and want to support its development, you can donate sats via
|
|||||||
|
|
||||||
- [x] Threads/social
|
- [x] Threads/social
|
||||||
- [x] Profile search using NIP-50
|
- [x] Profile search using NIP-50
|
||||||
- [x] Login via extension
|
- [x] Login via extension, nsecbunker, and pubkey
|
||||||
- [x] Profile sharing via QR codes
|
- [x] Profile sharing via QR codes
|
||||||
- [x] NIP 05 verification
|
- [x] NIP 05 verification
|
||||||
- [x] Bech32 entity search
|
- [x] NIP 65 relay selection and NIP 32 relay reviews
|
||||||
- [x] Notifications
|
- [x] NIP 89 app recommendations
|
||||||
- [x] Chat and direct messages
|
- [x] NIP 32 labeling and recommendations
|
||||||
|
- [x] Bech32 entity search and scan
|
||||||
|
- [x] Mention, reply, and reaction notifications
|
||||||
|
- [x] Direct messages - NIP 04 and NIP 24
|
||||||
- [x] Note composition with mentions and topics
|
- [x] Note composition with mentions and topics
|
||||||
- [x] Profile pages, follow/unfollow
|
- [x] Content warnings, mute, and keyword mute
|
||||||
- [x] Thread and person muting, collapse thread
|
- [x] Profile pages, follow/unfollow, follow/follower count
|
||||||
- [x] Smart relay selection and display
|
- [x] Thread muting, collapse thread
|
||||||
- [x] Invoice, quote, mention, link, image, and video rendering
|
- [x] Invoice, quote, mention, link, image, and video rendering
|
||||||
- [x] Installable as a progressive web app
|
- [x] Installable as a progressive web app
|
||||||
- [x] Integrated media uploads
|
- [x] Integrated media uploads via nostr.build
|
||||||
- [x] Lightning zaps
|
- [x] Lightning zaps and reactions
|
||||||
- [x] Feeds customizable by person, relay, and topic using NIP-51
|
- [x] Feeds customizable by person, relay, and topic using NIP-51
|
||||||
- [x] AUTH (NIP-42) support for paid relays
|
- [x] AUTH (NIP-42) support for paid relays
|
||||||
- [x] Multiplextr support for reducing bandwidth
|
- [x] Multiplextr support for reducing bandwidth
|
||||||
- [x] Profile and note metadata
|
- [x] Profile and note metadata
|
||||||
- [x] White-labeling support
|
- [x] White-labeling support
|
||||||
- [ ] Exportable copy of all user events
|
- [x] NIP 51 person lists
|
||||||
|
- [x] Exportable copy of all user events
|
||||||
- [ ] Reporting and basic distributed moderation
|
- [ ] Reporting and basic distributed moderation
|
||||||
- [ ] Content and person recommendations
|
- [ ] Content and person recommendations
|
||||||
|
- [ ] Private groups including administration, moderation, and membership
|
||||||
|
- [ ] Cross-posting between groups and public nostr
|
||||||
|
- [ ] Public and private calendar events
|
||||||
|
- [ ] Public and private marketplaces
|
||||||
|
|
||||||
You can find a more complete changelog [here](./CHANGELOG.md).
|
You can find a more complete changelog [here](./CHANGELOG.md).
|
||||||
|
|
||||||
|
@ -10,9 +10,7 @@
|
|||||||
let scrollY = 0
|
let scrollY = 0
|
||||||
let playerIsOpen = false
|
let playerIsOpen = false
|
||||||
|
|
||||||
$: showButtons = !$location.pathname.match(
|
$: showButtons = !$location.pathname.match(/conversations|channels|relays|keys|settings|logout$/)
|
||||||
/conversations|channels|chat|relays|keys|settings|logout$/
|
|
||||||
)
|
|
||||||
|
|
||||||
const scrollToTop = () => document.body.scrollIntoView({behavior: "smooth"})
|
const scrollToTop = () => document.body.scrollIntoView({behavior: "smooth"})
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import {nip19} from "nostr-tools"
|
import {nip19} from "nostr-tools"
|
||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
import Spinner from "src/partials/Spinner.svelte"
|
import Spinner from "src/partials/Spinner.svelte"
|
||||||
import ChatEdit from "src/app/views/ChatEdit.svelte"
|
|
||||||
import ChannelCreate from "src/app/views/ChannelCreate.svelte"
|
import ChannelCreate from "src/app/views/ChannelCreate.svelte"
|
||||||
import Login from "src/app/views/Login.svelte"
|
import Login from "src/app/views/Login.svelte"
|
||||||
import LoginConnect from "src/app/views/LoginConnect.svelte"
|
import LoginConnect from "src/app/views/LoginConnect.svelte"
|
||||||
@ -61,8 +60,6 @@
|
|||||||
<Onboarding stage={m.stage} />
|
<Onboarding stage={m.stage} />
|
||||||
{:else if m.type === "channel/create"}
|
{:else if m.type === "channel/create"}
|
||||||
<ChannelCreate />
|
<ChannelCreate />
|
||||||
{:else if m.type === "chat/edit"}
|
|
||||||
<ChatEdit {...m} />
|
|
||||||
{:else if m.type === "login/intro"}
|
{:else if m.type === "login/intro"}
|
||||||
<Login />
|
<Login />
|
||||||
{:else if m.type === "login/privkey"}
|
{:else if m.type === "login/privkey"}
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
import {base64DecodeOrPlainWebSocketURL} from "src/util/misc"
|
import {base64DecodeOrPlainWebSocketURL} from "src/util/misc"
|
||||||
import Notifications from "src/app/views/Notifications.svelte"
|
import Notifications from "src/app/views/Notifications.svelte"
|
||||||
import Bech32Entity from "src/app/views/Bech32Entity.svelte"
|
import Bech32Entity from "src/app/views/Bech32Entity.svelte"
|
||||||
import ChatDetail from "src/app/views/ChatDetail.svelte"
|
|
||||||
import ChatList from "src/app/views/ChatList.svelte"
|
|
||||||
import Feeds from "src/app/views/Feeds.svelte"
|
import Feeds from "src/app/views/Feeds.svelte"
|
||||||
import Explore from "src/app/views/Explore.svelte"
|
import Explore from "src/app/views/Explore.svelte"
|
||||||
import UserKeys from "src/app/views/UserKeys.svelte"
|
import UserKeys from "src/app/views/UserKeys.svelte"
|
||||||
@ -47,12 +45,6 @@
|
|||||||
<PersonDetail npub={params.npub} />
|
<PersonDetail npub={params.npub} />
|
||||||
{/key}
|
{/key}
|
||||||
</TypedRoute>
|
</TypedRoute>
|
||||||
<TypedRoute path="/chat" component={ChatList} />
|
|
||||||
<TypedRoute path="/chat/:entity" let:params>
|
|
||||||
{#key params.entity}
|
|
||||||
<ChatDetail entity={params.entity} />
|
|
||||||
{/key}
|
|
||||||
</TypedRoute>
|
|
||||||
<TypedRoute path="/conversations">
|
<TypedRoute path="/conversations">
|
||||||
<MessagesList activeTab="conversations" />
|
<MessagesList activeTab="conversations" />
|
||||||
</TypedRoute>
|
</TypedRoute>
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import {modal, theme, installPrompt} from "src/partials/state"
|
import {modal, theme, installPrompt} from "src/partials/state"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import {
|
import {
|
||||||
hasNewNip28Messages,
|
|
||||||
hasNewNip04Messages,
|
hasNewNip04Messages,
|
||||||
hasNewNip24Messages,
|
hasNewNip24Messages,
|
||||||
hasNewNotifications,
|
hasNewNotifications,
|
||||||
@ -87,15 +86,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
<li class="relative">
|
<li class="cursor-pointer">
|
||||||
<a
|
<a
|
||||||
class="block px-4 py-2 transition-all hover:bg-accent hover:text-white"
|
class="block px-4 py-2 transition-all hover:bg-accent hover:text-white"
|
||||||
on:click={openNip28Chat}>
|
on:click={openNip28Chat}>
|
||||||
<i class="fa fa-comment mr-2" /> Chat
|
<i class="fa fa-comment mr-2" /> Chat
|
||||||
{#if $hasNewNip28Messages}
|
|
||||||
<div
|
|
||||||
class="absolute left-7 top-2 h-2 w-2 rounded border border-solid border-white bg-accent" />
|
|
||||||
{/if}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="cursor-pointer">
|
<li class="cursor-pointer">
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
peopleWithName,
|
peopleWithName,
|
||||||
session,
|
session,
|
||||||
canUseGiftWrap,
|
canUseGiftWrap,
|
||||||
hasNewNip28Messages,
|
|
||||||
hasNewNip04Messages,
|
hasNewNip04Messages,
|
||||||
hasNewNip24Messages,
|
hasNewNip24Messages,
|
||||||
hasNewNotifications,
|
hasNewNotifications,
|
||||||
@ -183,7 +182,7 @@
|
|||||||
<div class="app-logo flex cursor-pointer items-center gap-2" on:click={toggleMenu}>
|
<div class="app-logo flex cursor-pointer items-center gap-2" on:click={toggleMenu}>
|
||||||
<img alt="App Logo" src={logoUrl} class="w-10" />
|
<img alt="App Logo" src={logoUrl} class="w-10" />
|
||||||
<h1 class="staatliches pt-1 text-3xl">{appName}</h1>
|
<h1 class="staatliches pt-1 text-3xl">{appName}</h1>
|
||||||
{#if $hasNewNotifications || $hasNewNip28Messages || hasNewDMs}
|
{#if $hasNewNotifications || hasNewDMs}
|
||||||
<div
|
<div
|
||||||
class="absolute left-8 top-4 h-2 w-2 rounded border border-solid border-white bg-accent" />
|
class="absolute left-8 top-4 h-2 w-2 rounded border border-solid border-white bg-accent" />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,33 +1,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defaultTo} from "ramda"
|
|
||||||
import {navigate} from "svelte-routing"
|
|
||||||
import {nip19} from "nostr-tools"
|
import {nip19} from "nostr-tools"
|
||||||
import {tryJson} from "src/util/misc"
|
import {tryJson} from "src/util/misc"
|
||||||
import {Tags} from "src/util/nostr"
|
import {Tags} from "src/util/nostr"
|
||||||
import Card from "src/partials/Card.svelte"
|
import Card from "src/partials/Card.svelte"
|
||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
import ImageCircle from "src/partials/ImageCircle.svelte"
|
import ImageCircle from "src/partials/ImageCircle.svelte"
|
||||||
import {channels} from "src/engine"
|
|
||||||
|
|
||||||
export let note
|
export let note
|
||||||
|
|
||||||
const {name, picture, about} = tryJson(() => JSON.parse(note.content))
|
const {name, picture, about} = tryJson(() => JSON.parse(note.content))
|
||||||
const channel = channels
|
|
||||||
.key(note.id)
|
|
||||||
.derived(defaultTo({id: note.id, meta: {name, picture, about}}))
|
|
||||||
const noteId = nip19.noteEncode(note.kind === 40 ? note.id : Tags.from(note).getMeta("e"))
|
const noteId = nip19.noteEncode(note.kind === 40 ? note.id : Tags.from(note).getMeta("e"))
|
||||||
|
|
||||||
|
const goToChat = () => window.open(`https://chat.coracle.social/chat/${noteId}`)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card interactive invertColors on:click={() => navigate(`/chat/${noteId}`)}>
|
<Card interactive invertColors on:click={goToChat}>
|
||||||
<Content>
|
<Content>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
{#if $channel.meta?.picture}
|
{#if picture}
|
||||||
<ImageCircle size={10} src={$channel.meta?.picture} />
|
<ImageCircle size={10} src={picture} />
|
||||||
{/if}
|
{/if}
|
||||||
<h3 class="staatliches text-2xl">{$channel.meta?.name}</h3>
|
<h3 class="staatliches text-2xl">{name}</h3>
|
||||||
</div>
|
</div>
|
||||||
{#if $channel.meta?.about}
|
{#if about}
|
||||||
<p>{$channel.meta?.about}</p>
|
<p>{about}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</Content>
|
</Content>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {onMount, onDestroy} from "svelte"
|
|
||||||
import {assoc} from "ramda"
|
|
||||||
import {formatTimestamp} from "src/util/misc"
|
|
||||||
import {toHex} from "src/util/nostr"
|
|
||||||
import {modal} from "src/partials/state"
|
|
||||||
import Channel from "src/partials/Channel.svelte"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
|
||||||
import ImageCircle from "src/partials/ImageCircle.svelte"
|
|
||||||
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
|
|
||||||
import NoteContent from "src/app/shared/NoteContent.svelte"
|
|
||||||
import {
|
|
||||||
canSign,
|
|
||||||
session,
|
|
||||||
channels,
|
|
||||||
imgproxy,
|
|
||||||
publishNip28Message,
|
|
||||||
joinNip28Channel,
|
|
||||||
leaveNip28Channel,
|
|
||||||
listenForNip28Messages,
|
|
||||||
publishNip28ChannelChecked,
|
|
||||||
} from "src/engine"
|
|
||||||
|
|
||||||
export let entity
|
|
||||||
|
|
||||||
const id = toHex(entity)
|
|
||||||
const channel = channels.key(id)
|
|
||||||
|
|
||||||
publishNip28ChannelChecked(id)
|
|
||||||
|
|
||||||
const join = () => joinNip28Channel(id)
|
|
||||||
|
|
||||||
const leave = () => leaveNip28Channel(id)
|
|
||||||
|
|
||||||
const edit = () => modal.push({type: "chat/edit", channel: $channel})
|
|
||||||
|
|
||||||
const sendMessage = async content => {
|
|
||||||
const pub = await publishNip28Message(id, content)
|
|
||||||
|
|
||||||
return pub.result
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const sub = listenForNip28Messages(id)
|
|
||||||
|
|
||||||
return () => sub.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
publishNip28ChannelChecked(id)
|
|
||||||
|
|
||||||
// Save on memory by deleting messages we don't care about
|
|
||||||
if (!$channel?.nip28?.joined) {
|
|
||||||
channel.update(assoc("messages", []))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
$: picture = imgproxy($channel?.meta?.picture, {w: 96, h: 96})
|
|
||||||
|
|
||||||
document.title = $channel?.meta?.name || "Coracle Chat"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Channel messages={$channel?.messages || []} {sendMessage}>
|
|
||||||
<div slot="header" class="flex h-16 items-start gap-4 overflow-hidden p-2">
|
|
||||||
<div class="flex items-center gap-4 pt-1">
|
|
||||||
<Anchor type="unstyled" class="fa fa-arrow-left cursor-pointer text-2xl" href="/chat" />
|
|
||||||
<ImageCircle size={10} src={picture} />
|
|
||||||
</div>
|
|
||||||
<div class="flex h-12 flex-grow flex-col overflow-hidden pt-px">
|
|
||||||
<div class="font-bold">{$channel?.meta?.name || ""}</div>
|
|
||||||
<div>{$channel?.meta?.about || ""}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex h-12 flex-col pt-px">
|
|
||||||
<div class="flex w-full items-center justify-between">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
{#if $channel?.nip28?.owner === $session?.pubkey}
|
|
||||||
<button class="cursor-pointer text-sm" on:click={edit}>
|
|
||||||
<i class="fa-solid fa-edit" /> Edit
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{#if $channel?.nip28?.joined}
|
|
||||||
<Anchor theme="button" killEvent class="flex items-center gap-2" on:click={leave}>
|
|
||||||
<i class="fa fa-right-from-bracket" />
|
|
||||||
<span>Leave</span>
|
|
||||||
</Anchor>
|
|
||||||
{:else if $canSign}
|
|
||||||
<Anchor theme="button" killEvent class="flex items-center gap-2" on:click={join}>
|
|
||||||
<i class="fa fa-right-to-bracket" />
|
|
||||||
<span>Join</span>
|
|
||||||
</Anchor>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div slot="message" let:message>
|
|
||||||
{#if message.showProfile}
|
|
||||||
<div class="flex items-center justify-between gap-4">
|
|
||||||
<PersonBadgeSmall pubkey={message.pubkey} />
|
|
||||||
<p class="text-sm text-gray-1">{formatTimestamp(message.created_at)}</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="my-1 ml-6 overflow-hidden text-ellipsis">
|
|
||||||
<NoteContent showEntire note={message} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Channel>
|
|
@ -1,64 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Input from "src/partials/Input.svelte"
|
|
||||||
import Content from "src/partials/Content.svelte"
|
|
||||||
import Textarea from "src/partials/Textarea.svelte"
|
|
||||||
import ImageInput from "src/partials/ImageInput.svelte"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
|
||||||
import {toast, modal} from "src/partials/state"
|
|
||||||
import {joinNip28Channel, publishNip28ChannelCreate, publishNip28ChannelUpdate} from "src/engine"
|
|
||||||
import {toastProgress} from "src/app/state"
|
|
||||||
|
|
||||||
export let channel = {name: null, id: null, about: null, picture: null}
|
|
||||||
|
|
||||||
const submit = async e => {
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
if (!channel.name) {
|
|
||||||
toast.show("error", "Please enter a name for your room.")
|
|
||||||
} else {
|
|
||||||
const {id, ...content} = channel
|
|
||||||
|
|
||||||
if (id) {
|
|
||||||
const pub = await publishNip28ChannelUpdate(id, content)
|
|
||||||
|
|
||||||
pub.on("progress", toastProgress)
|
|
||||||
} else {
|
|
||||||
const pub = await publishNip28ChannelCreate(content)
|
|
||||||
|
|
||||||
joinNip28Channel(pub.event.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
modal.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form on:submit={submit} class="flex justify-center py-12">
|
|
||||||
<Content>
|
|
||||||
<div class="mb-4 flex flex-col items-center justify-center">
|
|
||||||
<h1 class="staatliches text-6xl">Name your room</h1>
|
|
||||||
</div>
|
|
||||||
<div class="flex w-full flex-col gap-8">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<strong>Room name</strong>
|
|
||||||
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={channel.name}>
|
|
||||||
<i slot="before" class="fa-solid fa-tag" />
|
|
||||||
</Input>
|
|
||||||
<p class="text-sm text-gray-1">The room's name can be changed anytime.</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<strong>Room information</strong>
|
|
||||||
<Textarea name="about" bind:value={channel.about} />
|
|
||||||
<p class="text-sm text-gray-1">
|
|
||||||
Give people an idea of what kind of conversations will be happening here.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<strong>Picture</strong>
|
|
||||||
<ImageInput icon="image" bind:value={channel.picture} />
|
|
||||||
<p class="text-sm text-gray-1">A picture to help people remember your room.</p>
|
|
||||||
</div>
|
|
||||||
<Anchor tag="button" theme="button" type="submit" class="text-center">Done</Anchor>
|
|
||||||
</div>
|
|
||||||
</Content>
|
|
||||||
</form>
|
|
@ -1,130 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {onMount} from "svelte"
|
|
||||||
import {batch, seconds} from "hurdak"
|
|
||||||
import {pluck, filter, uniq, path} from "ramda"
|
|
||||||
import {now, createScroller} from "src/util/misc"
|
|
||||||
import {Tags} from "src/util/nostr"
|
|
||||||
import {modal} from "src/partials/state"
|
|
||||||
import Input from "src/partials/Input.svelte"
|
|
||||||
import Content from "src/partials/Content.svelte"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
|
||||||
import ChatListItem from "src/app/views/ChatListItem.svelte"
|
|
||||||
import type {Event, Filter} from "src/engine"
|
|
||||||
import {
|
|
||||||
session,
|
|
||||||
canSign,
|
|
||||||
follows,
|
|
||||||
stateKey,
|
|
||||||
searchNip28Channels,
|
|
||||||
load,
|
|
||||||
getPubkeyHints,
|
|
||||||
searchableRelays,
|
|
||||||
sortChannels,
|
|
||||||
nip28ChannelsWithMeta,
|
|
||||||
loadPubkeys,
|
|
||||||
getPubkeysWithDefaults,
|
|
||||||
} from "src/engine"
|
|
||||||
|
|
||||||
let q = ""
|
|
||||||
let results = []
|
|
||||||
let limit = 5
|
|
||||||
|
|
||||||
const loadMore = async () => {
|
|
||||||
limit += 5
|
|
||||||
}
|
|
||||||
|
|
||||||
const scroller = createScroller(loadMore)
|
|
||||||
|
|
||||||
const channels = nip28ChannelsWithMeta.derived(sortChannels)
|
|
||||||
|
|
||||||
const joined = channels.derived(filter(path(["nip28", "joined"])))
|
|
||||||
|
|
||||||
const searchChannels = q => {
|
|
||||||
if (q.length > 3) {
|
|
||||||
load({
|
|
||||||
relays: $searchableRelays,
|
|
||||||
filters: [{kinds: [40, 41], search: q}],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: searchChannels(q)
|
|
||||||
$: results = $searchNip28Channels(q).slice(0, limit)
|
|
||||||
|
|
||||||
document.title = "Chat"
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const relays = getPubkeyHints.limit(3).getHints($stateKey, "read")
|
|
||||||
const authors = getPubkeysWithDefaults($follows)
|
|
||||||
const since = now() - seconds(1, "day")
|
|
||||||
const filters = [
|
|
||||||
{kinds: [40, 41], authors, limit: 100},
|
|
||||||
{kinds: [42], since, authors, limit: 100},
|
|
||||||
] as Filter[]
|
|
||||||
|
|
||||||
if ($session) {
|
|
||||||
filters.push({kinds: [40, 41], authors: [$session.pubkey]})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull some relevant channels by grabbing recent messages
|
|
||||||
load({
|
|
||||||
relays,
|
|
||||||
filters,
|
|
||||||
onEvent: batch(500, (events: Event[]) => {
|
|
||||||
const channelIds = uniq(
|
|
||||||
events.filter(e => e.kind === 42).map(e => Tags.from(e).getMeta("e"))
|
|
||||||
)
|
|
||||||
|
|
||||||
loadPubkeys(pluck("pubkey", events))
|
|
||||||
|
|
||||||
if (channelIds.length > 0) {
|
|
||||||
load({
|
|
||||||
relays,
|
|
||||||
filters: [
|
|
||||||
{kinds: [40], ids: channelIds},
|
|
||||||
{kinds: [41], "#e": channelIds},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
scroller.stop()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Content>
|
|
||||||
{#if $canSign}
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<i class="fa fa-server fa-lg" />
|
|
||||||
<h2 class="staatliches text-2xl">Your rooms</h2>
|
|
||||||
</div>
|
|
||||||
<Anchor theme="button-accent" on:click={() => modal.push({type: "chat/edit"})}>
|
|
||||||
<i class="fa-solid fa-plus" /> Create Room
|
|
||||||
</Anchor>
|
|
||||||
</div>
|
|
||||||
{#each $joined as channel (channel.id)}
|
|
||||||
<ChatListItem {channel} />
|
|
||||||
{:else}
|
|
||||||
<p class="text-center py-8">You haven't yet joined any rooms.</p>
|
|
||||||
{/each}
|
|
||||||
<div class="mb-2 border-b border-solid border-gray-6 pt-2" />
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<i class="fa fa-earth-asia fa-lg" />
|
|
||||||
<h2 class="staatliches text-2xl">Other rooms</h2>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="flex-grow">
|
|
||||||
<Input bind:value={q} type="text" placeholder="Search rooms">
|
|
||||||
<i slot="before" class="fa-solid fa-search" />
|
|
||||||
</Input>
|
|
||||||
</div>
|
|
||||||
{#each results as channel (channel.id)}
|
|
||||||
<ChatListItem {channel} />
|
|
||||||
{:else}
|
|
||||||
<small class="text-center"> No matching rooms found </small>
|
|
||||||
{/each}
|
|
||||||
</Content>
|
|
@ -1,57 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {nip19} from "nostr-tools"
|
|
||||||
import {navigate} from "svelte-routing"
|
|
||||||
import {fly} from "src/util/transition"
|
|
||||||
import {ellipsize} from "hurdak"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
|
||||||
import {canSign, hasNewMessages, imgproxy, joinNip28Channel, leaveNip28Channel} from "src/engine"
|
|
||||||
|
|
||||||
export let channel
|
|
||||||
|
|
||||||
const enter = () => navigate(`/chat/${nip19.noteEncode(channel.id)}`)
|
|
||||||
const join = () => joinNip28Channel(channel.id)
|
|
||||||
const leave = () => leaveNip28Channel(channel.id)
|
|
||||||
|
|
||||||
// Accommodate data urls from legacy
|
|
||||||
const picture =
|
|
||||||
channel.meta?.picture?.length > 500
|
|
||||||
? channel.meta.picture
|
|
||||||
: imgproxy(channel.meta.picture, {w: 112, h: 112})
|
|
||||||
|
|
||||||
$: showBadge = channel.nip28.joined && hasNewMessages(channel)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="relative flex cursor-pointer gap-4 rounded border border-solid border-gray-6 bg-gray-7 px-4 py-6 transition-all hover:bg-gray-6"
|
|
||||||
on:click={enter}
|
|
||||||
in:fly={{y: 20}}>
|
|
||||||
<div
|
|
||||||
class="h-14 w-14 shrink-0 overflow-hidden rounded-full border border-solid border-white bg-cover bg-center"
|
|
||||||
style={`background-image: url(${picture})`} />
|
|
||||||
{#if showBadge}
|
|
||||||
<div class="absolute left-2 top-2 h-2 w-2 rounded bg-accent" />
|
|
||||||
{/if}
|
|
||||||
<div class="flex min-w-0 flex-grow flex-col justify-start gap-2">
|
|
||||||
<div class="flex flex-grow items-start justify-between gap-2">
|
|
||||||
<h2 class="text-lg">
|
|
||||||
{channel.meta?.name || ""}
|
|
||||||
</h2>
|
|
||||||
{#if channel.nip28.joined}
|
|
||||||
<Anchor theme="button" killEvent class="flex items-center gap-2" on:click={leave}>
|
|
||||||
<i class="fa fa-right-from-bracket" />
|
|
||||||
<span>Leave</span>
|
|
||||||
</Anchor>
|
|
||||||
{:else if $canSign}
|
|
||||||
<Anchor theme="button" killEvent class="flex items-center gap-2" on:click={join}>
|
|
||||||
<i class="fa fa-right-to-bracket" />
|
|
||||||
<span>Join</span>
|
|
||||||
</Anchor>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if channel.meta?.about}
|
|
||||||
<p class="text-start text-gray-1">
|
|
||||||
{ellipsize(channel.meta.about, 300)}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
@ -1,11 +1,11 @@
|
|||||||
import {generatePrivateKey} from "nostr-tools"
|
import {generatePrivateKey} from "nostr-tools"
|
||||||
import {assoc, assocPath, path, prop, fromPairs, pluck, whereEq, when, map} from "ramda"
|
import {assoc, whereEq, when, map} from "ramda"
|
||||||
import {createMapOf} from "hurdak"
|
import {createMapOf} from "hurdak"
|
||||||
import {now} from "src/util/misc"
|
import {now} from "src/util/misc"
|
||||||
import {appDataKeys} from "src/util/nostr"
|
import {appDataKeys} from "src/util/nostr"
|
||||||
import {EventKind} from "src/engine/events/model"
|
import {EventKind} from "src/engine/events/model"
|
||||||
import {Publisher, publishEvent, mention} from "src/engine/network/utils"
|
import {Publisher, publishEvent, mention} from "src/engine/network/utils"
|
||||||
import {selectHints, getInboxHints, getPubkeyHints} from "src/engine/relays/utils"
|
import {getInboxHints, getPubkeyHints} from "src/engine/relays/utils"
|
||||||
import {user, nip04, nip59} from "src/engine/session/derived"
|
import {user, nip04, nip59} from "src/engine/session/derived"
|
||||||
import {setAppData} from "src/engine/session/commands"
|
import {setAppData} from "src/engine/session/commands"
|
||||||
import {channels} from "./state"
|
import {channels} from "./state"
|
||||||
@ -79,45 +79,3 @@ export const nip24MarkChannelRead = (pubkey: string) => {
|
|||||||
|
|
||||||
publishNip24Read()
|
publishNip24Read()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const publishNip28ChannelCreate = content =>
|
|
||||||
publishEvent(40, {content: JSON.stringify(content)})
|
|
||||||
|
|
||||||
export const publishNip28ChannelUpdate = (id, content) =>
|
|
||||||
publishEvent(41, {content: JSON.stringify(content), tags: [["e", id]]})
|
|
||||||
|
|
||||||
export const publishNip28Message = (id, content) => {
|
|
||||||
const channel = channels.key(id).get()
|
|
||||||
const [hint] = selectHints(channel?.relays || [])
|
|
||||||
|
|
||||||
return publishEvent(42, {content, tags: [["e", id, hint, "root"]]})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const publishNip28ChannelChecked = (id: string) => {
|
|
||||||
const lastChecked = fromPairs(
|
|
||||||
channels
|
|
||||||
.get()
|
|
||||||
.filter(prop("last_checked"))
|
|
||||||
.map(r => [r.id, r.last_checked])
|
|
||||||
)
|
|
||||||
|
|
||||||
return setAppData(appDataKeys.NIP28_LAST_CHECKED, {...lastChecked, [id]: now()})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const saveNip28Channels = () =>
|
|
||||||
setAppData(
|
|
||||||
appDataKeys.NIP28_ROOMS_JOINED,
|
|
||||||
pluck("id", channels.get().filter(path(["nip28", "joined"])))
|
|
||||||
)
|
|
||||||
|
|
||||||
export const joinNip28Channel = (id: string) => {
|
|
||||||
channels.key(id).update(assocPath(["nip28", "joined"], true))
|
|
||||||
|
|
||||||
return saveNip28Channels()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const leaveNip28Channel = (id: string) => {
|
|
||||||
channels.key(id).update(assocPath(["nip28", "joined"], false))
|
|
||||||
|
|
||||||
return saveNip28Channels()
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {find, filter, path, whereEq} from "ramda"
|
import {find, filter, whereEq} from "ramda"
|
||||||
import {hasNewMessages, getChannelSearch} from "./utils"
|
import {hasNewMessages} from "./utils"
|
||||||
import type {Channel} from "./model"
|
|
||||||
import {channels} from "./state"
|
import {channels} from "./state"
|
||||||
|
|
||||||
// Nip04
|
// Nip04
|
||||||
@ -14,15 +13,3 @@ export const hasNewNip04Messages = nip04Channels.derived(find(hasNewMessages))
|
|||||||
export const nip24Channels = channels.throttle(300).derived(filter(whereEq({type: "nip24"})))
|
export const nip24Channels = channels.throttle(300).derived(filter(whereEq({type: "nip24"})))
|
||||||
|
|
||||||
export const hasNewNip24Messages = nip24Channels.derived(find(hasNewMessages))
|
export const hasNewNip24Messages = nip24Channels.derived(find(hasNewMessages))
|
||||||
|
|
||||||
// Nip28
|
|
||||||
|
|
||||||
export const nip28ChannelsWithMeta = channels
|
|
||||||
.throttle(300)
|
|
||||||
.derived(filter((c: Channel) => c.meta && c.type === "nip28"))
|
|
||||||
|
|
||||||
export const nip28ChannelsForUser = nip28ChannelsWithMeta.derived(filter(path(["nip28", "joined"])))
|
|
||||||
|
|
||||||
export const searchNip28Channels = nip28ChannelsWithMeta.derived(getChannelSearch)
|
|
||||||
|
|
||||||
export const hasNewNip28Messages = nip28ChannelsForUser.derived(find(hasNewMessages))
|
|
||||||
|
@ -2,23 +2,13 @@ import type {Event} from "src/engine/events/model"
|
|||||||
|
|
||||||
export type Channel = {
|
export type Channel = {
|
||||||
id: string
|
id: string
|
||||||
type: "nip28" | "nip04" | "nip44"
|
type: "nip04" | "nip44"
|
||||||
relays: string[]
|
relays: string[]
|
||||||
messages: Event[]
|
messages: Event[]
|
||||||
last_sent?: number
|
last_sent?: number
|
||||||
last_received?: number
|
last_received?: number
|
||||||
last_checked?: number
|
last_checked?: number
|
||||||
meta_updated_at?: number
|
|
||||||
meta?: {
|
|
||||||
name?: string
|
|
||||||
about?: string
|
|
||||||
picture?: string
|
|
||||||
}
|
|
||||||
nip04?: {
|
nip04?: {
|
||||||
plaintext: string
|
plaintext: string
|
||||||
}
|
}
|
||||||
nip28?: {
|
|
||||||
owner?: string
|
|
||||||
joined?: string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import {prop, map, assocPath, pluck, last, uniqBy, uniq} from "ramda"
|
import {prop, uniqBy, uniq} from "ramda"
|
||||||
import {tryFunc, sleep} from "hurdak"
|
import {tryFunc, sleep} from "hurdak"
|
||||||
import {tryJson} from "src/util/misc"
|
import {tryJson} from "src/util/misc"
|
||||||
import {Tags, appDataKeys} from "src/util/nostr"
|
import {Tags, appDataKeys} from "src/util/nostr"
|
||||||
import type {Event} from "src/engine/events/model"
|
|
||||||
import {EventKind} from "src/engine/events/model"
|
import {EventKind} from "src/engine/events/model"
|
||||||
import {sessions} from "src/engine/session/state"
|
import {sessions} from "src/engine/session/state"
|
||||||
import {Signer, Nip04, getNdk} from "src/engine/session/utils"
|
import {Signer, Nip04, getNdk} from "src/engine/session/utils"
|
||||||
import {getEventHints} from "src/engine/relays/utils"
|
import {getEventHints} from "src/engine/relays/utils"
|
||||||
import {projections} from "src/engine/core/projections"
|
import {projections} from "src/engine/core/projections"
|
||||||
import {updateRecord, updateStore} from "src/engine/core/commands"
|
|
||||||
import type {Channel} from "./model"
|
import type {Channel} from "./model"
|
||||||
import {channels} from "./state"
|
import {channels} from "./state"
|
||||||
import {getNip24ChannelId} from "./utils"
|
import {getNip24ChannelId} from "./utils"
|
||||||
@ -85,58 +83,6 @@ projections.addHandler(EventKind.AppData, async e => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
d === appDataKeys.NIP28_ROOMS_JOINED &&
|
|
||||||
e.created_at > (session.nip28_channels_last_joined || 0)
|
|
||||||
) {
|
|
||||||
sessions.update(assocPath([session.pubkey, "nip28_channels_last_joined"], e.created_at))
|
|
||||||
|
|
||||||
await tryJson(async () => {
|
|
||||||
const channelIds = JSON.parse(await nip04.decryptAsUser(e.content, e.pubkey))
|
|
||||||
|
|
||||||
// Just a bug from when I was building the feature, remove someday
|
|
||||||
if (!Array.isArray(channelIds)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channels.update(
|
|
||||||
map(channel => {
|
|
||||||
if (channel.nip28?.joined && !channelIds.includes(channel.id)) {
|
|
||||||
return assocPath(["nip28", "joined"], false, channel)
|
|
||||||
} else if (!channel.nip28?.joined && channelIds.includes(channel.id)) {
|
|
||||||
return assocPath(["nip28", "joined"], true, channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
return channel
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (d === appDataKeys.NIP28_LAST_CHECKED) {
|
|
||||||
await tryJson(async () => {
|
|
||||||
const payload = JSON.parse(await nip04.decryptAsUser(e.content, e.pubkey))
|
|
||||||
|
|
||||||
channels.mapStore.update($channels => {
|
|
||||||
for (const key of Object.keys(payload)) {
|
|
||||||
// Backwards compat from when we used to prefix id/pubkey
|
|
||||||
const id = last(key.split("/"))
|
|
||||||
const channel = $channels.get(id)
|
|
||||||
const last_checked = Math.max(payload[id], channel?.last_checked || 0)
|
|
||||||
|
|
||||||
// A bunch of junk got added to this setting. Integer keys, settings, etc
|
|
||||||
if (isNaN(last_checked) || last_checked < 1577836800) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
$channels.set(id, {...channel, id, last_checked})
|
|
||||||
}
|
|
||||||
|
|
||||||
return $channels
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
projections.addHandler(EventKind.Nip04Message, async e => {
|
projections.addHandler(EventKind.Nip04Message, async e => {
|
||||||
@ -210,65 +156,3 @@ projections.addHandler(EventKind.Nip44Message, e => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
projections.addHandler(EventKind.ChannelCreation, (e: Event) => {
|
|
||||||
const meta = tryJson(() => JSON.parse(e.content))
|
|
||||||
const relays = Tags.from(e).relays()
|
|
||||||
|
|
||||||
if (meta?.name) {
|
|
||||||
channels.key(e.id).update($channel => ({
|
|
||||||
...updateRecord($channel, e.created_at, {meta, relays}),
|
|
||||||
nip28: {...$channel?.nip28, owner: e.pubkey},
|
|
||||||
type: "nip28",
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
projections.addHandler(EventKind.ChannelMetadata, (e: Event) => {
|
|
||||||
const channelId = Tags.from(e).getMeta("e")
|
|
||||||
|
|
||||||
if (!channelId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel = channels.key(channelId).get()
|
|
||||||
|
|
||||||
if (e.pubkey !== channel?.nip28?.owner) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const meta = tryJson(() => JSON.parse(e.content))
|
|
||||||
const relays = Tags.from(e).relays()
|
|
||||||
|
|
||||||
if (meta?.name) {
|
|
||||||
updateStore(channels.key(channelId), e.created_at, {meta, relays})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
projections.addHandler(EventKind.ChannelMessage, (e: Event) => {
|
|
||||||
const tags = Tags.from(e)
|
|
||||||
const channelId = tags.getMeta("e")
|
|
||||||
const pubkeys = pluck("pubkey", Object.values(sessions.get()))
|
|
||||||
|
|
||||||
if (!channelId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channels.key(channelId).update($channel => {
|
|
||||||
const updates = {
|
|
||||||
...$channel,
|
|
||||||
id: channelId,
|
|
||||||
type: "nip28",
|
|
||||||
relays: uniq([...tags.relays(), ...($channel?.relays || [])]),
|
|
||||||
messages: uniqBy(prop("id"), [e, ...($channel?.messages || [])]),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pubkeys.includes(e.pubkey)) {
|
|
||||||
updates.last_sent = Math.max(updates.last_sent || 0, e.created_at)
|
|
||||||
} else {
|
|
||||||
updates.last_received = Math.max(updates.last_received || 0, e.created_at)
|
|
||||||
}
|
|
||||||
|
|
||||||
return updates as Channel
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
@ -3,8 +3,7 @@ import {batch, seconds} from "hurdak"
|
|||||||
import {EventKind} from "src/engine/events/model"
|
import {EventKind} from "src/engine/events/model"
|
||||||
import {pubkey} from "src/engine/session/state"
|
import {pubkey} from "src/engine/session/state"
|
||||||
import {load, loadPubkeys, subscribe} from "src/engine/network/utils"
|
import {load, loadPubkeys, subscribe} from "src/engine/network/utils"
|
||||||
import {getInboxHints, selectHints, getPubkeyHints, getUserRelayUrls} from "src/engine/relays/utils"
|
import {getInboxHints, getPubkeyHints, getUserRelayUrls} from "src/engine/relays/utils"
|
||||||
import {channels} from "./state"
|
|
||||||
import {getNip24ChannelPubkeys} from "./utils"
|
import {getNip24ChannelPubkeys} from "./utils"
|
||||||
import {nip24Channels, nip04Channels} from "./derived"
|
import {nip24Channels, nip04Channels} from "./derived"
|
||||||
|
|
||||||
@ -56,19 +55,6 @@ export const loadAllNip24Messages = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listenForNip28Messages = channelId => {
|
|
||||||
const channel = channels.key(channelId).get()
|
|
||||||
const relays = selectHints(channel?.relays || [])
|
|
||||||
|
|
||||||
return subscribe({
|
|
||||||
relays,
|
|
||||||
filters: [
|
|
||||||
{kinds: [40], ids: [channelId]},
|
|
||||||
{kinds: [41, 42], "#e": [channelId]},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const listenForNip59Messages = () => {
|
export const listenForNip59Messages = () => {
|
||||||
const $pubkey = pubkey.get()
|
const $pubkey = pubkey.get()
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {sortBy, uniq, identity} from "ramda"
|
import {sortBy, uniq, identity} from "ramda"
|
||||||
import {fuzzy} from "src/util/misc"
|
|
||||||
import type {Channel} from "./model"
|
import type {Channel} from "./model"
|
||||||
|
|
||||||
export const sortChannels = sortBy(
|
export const sortChannels = sortBy(
|
||||||
@ -9,9 +8,6 @@ export const sortChannels = sortBy(
|
|||||||
export const hasNewMessages = (c: Channel) =>
|
export const hasNewMessages = (c: Channel) =>
|
||||||
c.last_received > Math.max(c.last_sent || 0, c.last_checked || 0)
|
c.last_received > Math.max(c.last_sent || 0, c.last_checked || 0)
|
||||||
|
|
||||||
export const getChannelSearch = $channels =>
|
|
||||||
fuzzy($channels, {keys: ["meta.name", "meta.about"], threshold: 0.3})
|
|
||||||
|
|
||||||
export const getNip24ChannelId = (pubkeys: string[]) => sortBy(identity, uniq(pubkeys)).join(",")
|
export const getNip24ChannelId = (pubkeys: string[]) => sortBy(identity, uniq(pubkeys)).join(",")
|
||||||
|
|
||||||
export const getNip24ChannelPubkeys = (id: string) => id.split(",")
|
export const getNip24ChannelPubkeys = (id: string) => id.split(",")
|
||||||
|
@ -10,7 +10,6 @@ import {_events} from "src/engine/events/state"
|
|||||||
import {events, isEventMuted} from "src/engine/events/derived"
|
import {events, isEventMuted} from "src/engine/events/derived"
|
||||||
import {mergeHints, getPubkeyHints} from "src/engine/relays/utils"
|
import {mergeHints, getPubkeyHints} from "src/engine/relays/utils"
|
||||||
import {subscribe, subscribePersistent} from "src/engine/network/utils"
|
import {subscribe, subscribePersistent} from "src/engine/network/utils"
|
||||||
import {nip28ChannelsForUser} from "src/engine/channels/derived"
|
|
||||||
|
|
||||||
const onNotificationEvent = batch(300, (chunk: Event[]) => {
|
const onNotificationEvent = batch(300, (chunk: Event[]) => {
|
||||||
const kinds = getNotificationKinds()
|
const kinds = getNotificationKinds()
|
||||||
@ -62,8 +61,6 @@ export const loadNotifications = () => {
|
|||||||
|
|
||||||
export const listenForNotifications = async () => {
|
export const listenForNotifications = async () => {
|
||||||
const pubkeys = Object.keys(sessions.get())
|
const pubkeys = Object.keys(sessions.get())
|
||||||
const channelIds = pluck("id", nip28ChannelsForUser.get())
|
|
||||||
|
|
||||||
const relays = mergeHints(pubkeys.map(pk => getPubkeyHints(pk, "read")))
|
const relays = mergeHints(pubkeys.map(pk => getPubkeyHints(pk, "read")))
|
||||||
|
|
||||||
const eventIds: string[] = doPipe(events.get(), [
|
const eventIds: string[] = doPipe(events.get(), [
|
||||||
@ -82,11 +79,6 @@ export const listenForNotifications = async () => {
|
|||||||
{kinds: noteKinds, "#p": pubkeys, limit: 1},
|
{kinds: noteKinds, "#p": pubkeys, limit: 1},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Chat
|
|
||||||
if (channelIds.length > 0) {
|
|
||||||
filters.push({kinds: [42], "#e": channelIds, limit: 1})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replies
|
// Replies
|
||||||
if (eventIds.length > 0) {
|
if (eventIds.length > 0) {
|
||||||
filters.push({kinds: noteKinds, "#e": eventIds, limit: 1})
|
filters.push({kinds: noteKinds, "#e": eventIds, limit: 1})
|
||||||
|
@ -7,5 +7,4 @@ export type Session = {
|
|||||||
settings?: Record<string, any>
|
settings?: Record<string, any>
|
||||||
settings_updated_at?: number
|
settings_updated_at?: number
|
||||||
notifications_last_checked?: number
|
notifications_last_checked?: number
|
||||||
nip28_channels_last_joined?: number
|
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,6 @@ export const appDataKeys = {
|
|||||||
USER_SETTINGS: "nostr-engine/User/settings/v1",
|
USER_SETTINGS: "nostr-engine/User/settings/v1",
|
||||||
NIP04_LAST_CHECKED: "nostr-engine/Nip04/last_checked/v1",
|
NIP04_LAST_CHECKED: "nostr-engine/Nip04/last_checked/v1",
|
||||||
NIP24_LAST_CHECKED: "nostr-engine/Nip24/last_checked/v1",
|
NIP24_LAST_CHECKED: "nostr-engine/Nip24/last_checked/v1",
|
||||||
NIP28_LAST_CHECKED: "nostr-engine/Nip28/last_checked/v1",
|
|
||||||
NIP28_ROOMS_JOINED: "nostr-engine/Nip28/rooms_joined/v1",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tags {
|
export class Tags {
|
||||||
|
Loading…
Reference in New Issue
Block a user