chore: remove nip28 support
This commit is contained in:
@ -45,8 +45,7 @@
|
|||||||
"wss://relay.damus.io/": { "read": true, "write": true }
|
"wss://relay.damus.io/": { "read": true, "write": true }
|
||||||
},
|
},
|
||||||
"chatChannels": [
|
"chatChannels": [
|
||||||
{ "type": "telegram", "value": "https://t.me/irismessenger" },
|
{ "type": "telegram", "value": "https://t.me/irismessenger" }
|
||||||
{ "type": "nip28", "value": "23286a4602ada10cc10200553bff62a110e8dc0eacddf73277395a89ddf26a09" }
|
|
||||||
],
|
],
|
||||||
"alby": {
|
"alby": {
|
||||||
"clientId": "5rYcHDrlDb",
|
"clientId": "5rYcHDrlDb",
|
||||||
|
2
packages/app/custom.d.ts
vendored
2
packages/app/custom.d.ts
vendored
@ -101,7 +101,7 @@ declare const CONFIG: {
|
|||||||
|
|
||||||
// public chat channels for site
|
// public chat channels for site
|
||||||
chatChannels?: Array<{
|
chatChannels?: Array<{
|
||||||
type: "nip28" | "telegram";
|
type: "telegram";
|
||||||
value: string;
|
value: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { unwrap } from "@snort/shared";
|
|
||||||
import { decodeTLV, EventKind, NostrPrefix, RequestBuilder, TLVEntryType } from "@snort/system";
|
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
|
|
||||||
import { createEmptyChatObject } from "@/chat";
|
|
||||||
|
|
||||||
export function useEmptyChatSystem(id?: string) {
|
|
||||||
const sub = useMemo(() => {
|
|
||||||
if (id?.startsWith(NostrPrefix.Chat28)) {
|
|
||||||
const cx = unwrap(decodeTLV(id).find(a => a.type === TLVEntryType.Special)).value as string;
|
|
||||||
const rb = new RequestBuilder(`nip28:${id}`);
|
|
||||||
rb.withFilter().ids([cx]).kinds([EventKind.PublicChatChannel, EventKind.PublicChatMetadata]);
|
|
||||||
rb.withFilter()
|
|
||||||
.tag("e", [cx])
|
|
||||||
.kinds([EventKind.PublicChatChannel, EventKind.PublicChatMessage, EventKind.PublicChatMetadata]);
|
|
||||||
|
|
||||||
return rb;
|
|
||||||
} else {
|
|
||||||
return new RequestBuilder(id ?? "");
|
|
||||||
}
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
const data = useRequestBuilder(sub);
|
|
||||||
return useMemo(() => {
|
|
||||||
if (!id) return;
|
|
||||||
return createEmptyChatObject(id, data);
|
|
||||||
}, [id, data.length]);
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import Telegram from "@/assets/img/telegram.svg";
|
import Telegram from "@/assets/img/telegram.svg";
|
||||||
import { Nip28ChatSystem } from "@/chat/nip28";
|
|
||||||
import AsyncButton from "@/Components/Button/AsyncButton";
|
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||||
import Copy from "@/Components/Copy/Copy";
|
import Copy from "@/Components/Copy/Copy";
|
||||||
import ZapButton from "@/Components/Event/ZapButton";
|
import ZapButton from "@/Components/Event/ZapButton";
|
||||||
@ -105,18 +104,6 @@ const DonatePage = () => {
|
|||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "nip28": {
|
|
||||||
return (
|
|
||||||
<AsyncButton
|
|
||||||
onClick={() => {
|
|
||||||
const id = Nip28ChatSystem.chatId(a.value);
|
|
||||||
navigate(`/messages/${id}`);
|
|
||||||
}}>
|
|
||||||
<img src={CONFIG.icon} width={24} height={24} className="rounded-full" />
|
|
||||||
<FormattedMessage defaultMessage="Nostr Public Chat" />
|
|
||||||
</AsyncButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
import { decodeTLV, EventKind, NostrPrefix } from "@snort/system";
|
|
||||||
import { useUserSearch } from "@snort/system-react";
|
import { useUserSearch } from "@snort/system-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { ChatType, createChatLink } from "@/chat";
|
import { ChatType, createChatLink } from "@/chat";
|
||||||
import { Nip28ChatSystem } from "@/chat/nip28";
|
|
||||||
import Icon from "@/Components/Icons/Icon";
|
import Icon from "@/Components/Icons/Icon";
|
||||||
import Modal from "@/Components/Modal/Modal";
|
import Modal from "@/Components/Modal/Modal";
|
||||||
import ProfileImage from "@/Components/User/ProfileImage";
|
import ProfileImage from "@/Components/User/ProfileImage";
|
||||||
import ProfilePreview from "@/Components/User/ProfilePreview";
|
import ProfilePreview from "@/Components/User/ProfilePreview";
|
||||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
|
||||||
import useFollowsControls from "@/Hooks/useFollowControls";
|
import useFollowsControls from "@/Hooks/useFollowControls";
|
||||||
import useLogin from "@/Hooks/useLogin";
|
|
||||||
import Nip28ChatProfile from "@/Pages/Messages/Nip28ChatProfile";
|
|
||||||
import { appendDedupe, debounce } from "@/Utils";
|
import { appendDedupe, debounce } from "@/Utils";
|
||||||
import { LoginSession, LoginStore } from "@/Utils/Login";
|
|
||||||
|
|
||||||
export default function NewChatWindow() {
|
export default function NewChatWindow() {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
@ -24,8 +18,6 @@ export default function NewChatWindow() {
|
|||||||
const [term, setSearchTerm] = useState("");
|
const [term, setSearchTerm] = useState("");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const search = useUserSearch();
|
const search = useUserSearch();
|
||||||
const login = useLogin();
|
|
||||||
const { system, publisher } = useEventPublisher();
|
|
||||||
const { followList } = useFollowsControls();
|
const { followList } = useFollowsControls();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -115,32 +107,6 @@ export default function NewChatWindow() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{results.length === 1 && (
|
|
||||||
<Nip28ChatProfile
|
|
||||||
id={results[0]}
|
|
||||||
onClick={async id => {
|
|
||||||
setShow(false);
|
|
||||||
const chats = appendDedupe(login.extraChats, [Nip28ChatSystem.chatId(id)]);
|
|
||||||
LoginStore.updateSession({
|
|
||||||
...login,
|
|
||||||
extraChats: chats,
|
|
||||||
} as LoginSession);
|
|
||||||
const evList = await publisher?.generic(eb => {
|
|
||||||
eb.kind(EventKind.PublicChatsList);
|
|
||||||
chats.forEach(c => {
|
|
||||||
if (c.startsWith(NostrPrefix.Chat28)) {
|
|
||||||
eb.tag(["e", decodeTLV(c)[0].value as string]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return eb;
|
|
||||||
});
|
|
||||||
if (evList) {
|
|
||||||
await system.BroadcastEvent(evList);
|
|
||||||
}
|
|
||||||
navigate(createChatLink(ChatType.PublicGroupChat, id));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { NostrLink, UserMetadata } from "@snort/system";
|
|
||||||
import { useEventFeed } from "@snort/system-react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import ProfilePreview from "@/Components/User/ProfilePreview";
|
|
||||||
|
|
||||||
export default function Nip28ChatProfile({ id, onClick }: { id: string; onClick: (id: string) => void }) {
|
|
||||||
const channel = useEventFeed(new NostrLink(CONFIG.eventLinkPrefix, id, 40));
|
|
||||||
if (channel) {
|
|
||||||
const meta = JSON.parse(channel.content) as UserMetadata;
|
|
||||||
return (
|
|
||||||
<ProfilePreview
|
|
||||||
pubkey=""
|
|
||||||
profile={meta}
|
|
||||||
profileImageProps={{
|
|
||||||
link: "",
|
|
||||||
}}
|
|
||||||
options={{ about: false }}
|
|
||||||
actions={<></>}
|
|
||||||
onClick={() => onClick(id)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,6 @@ import {
|
|||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
|
||||||
import { useEmptyChatSystem } from "@/Hooks/useEmptyChatSystem";
|
|
||||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
import useModeration from "@/Hooks/useModeration";
|
import useModeration from "@/Hooks/useModeration";
|
||||||
@ -23,7 +22,6 @@ import { findTag } from "@/Utils";
|
|||||||
import { LoginSession } from "@/Utils/Login";
|
import { LoginSession } from "@/Utils/Login";
|
||||||
|
|
||||||
import { Nip17Chats, Nip17ChatSystem } from "./nip17";
|
import { Nip17Chats, Nip17ChatSystem } from "./nip17";
|
||||||
import { Nip28Chats, Nip28ChatSystem } from "./nip28";
|
|
||||||
|
|
||||||
export enum ChatType {
|
export enum ChatType {
|
||||||
PublicGroupChat = 2,
|
PublicGroupChat = 2,
|
||||||
@ -135,20 +133,14 @@ export function createChatLink(type: ChatType, ...params: Array<string>) {
|
|||||||
),
|
),
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
case ChatType.PublicGroupChat: {
|
|
||||||
return `/messages/${Nip28ChatSystem.chatId(params[0])}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
throw new Error("Unknown chat type");
|
throw new Error("Unknown chat type");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEmptyChatObject(id: string, messages?: Array<TaggedNostrEvent>) {
|
export function createEmptyChatObject(id: string) {
|
||||||
if (id.startsWith(NostrPrefix.Chat17)) {
|
if (id.startsWith(NostrPrefix.Chat17)) {
|
||||||
return Nip17ChatSystem.createChatObj(id, []);
|
return Nip17ChatSystem.createChatObj(id, []);
|
||||||
}
|
}
|
||||||
if (id.startsWith(NostrPrefix.Chat28)) {
|
|
||||||
return Nip28ChatSystem.createChatObj(id, messages ?? []);
|
|
||||||
}
|
|
||||||
throw new Error("Cant create new empty chat, unknown id");
|
throw new Error("Cant create new empty chat, unknown id");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,10 +171,9 @@ export function useChatSystem(chat: ChatSystem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useChatSystems() {
|
export function useChatSystems() {
|
||||||
const nip28 = useChatSystem(Nip28Chats);
|
|
||||||
const nip17 = useChatSystem(Nip17Chats);
|
const nip17 = useChatSystem(Nip17Chats);
|
||||||
|
|
||||||
return [...nip28, ...nip17];
|
return nip17;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useChat(id: string) {
|
export function useChat(id: string) {
|
||||||
@ -190,12 +181,8 @@ export function useChat(id: string) {
|
|||||||
if (id.startsWith(NostrPrefix.Chat17)) {
|
if (id.startsWith(NostrPrefix.Chat17)) {
|
||||||
return Nip17Chats;
|
return Nip17Chats;
|
||||||
}
|
}
|
||||||
if (id.startsWith(NostrPrefix.Chat28)) {
|
|
||||||
return Nip28Chats;
|
|
||||||
}
|
|
||||||
throw new Error("Unsupported chat system");
|
throw new Error("Unsupported chat system");
|
||||||
};
|
};
|
||||||
const ret = useChatSystem(getStore()).find(a => a.id === id);
|
const ret = useChatSystem(getStore()).find(a => a.id === id);
|
||||||
const emptyChat = useEmptyChatSystem(ret === undefined ? id : undefined);
|
return ret;
|
||||||
return ret ?? emptyChat;
|
|
||||||
}
|
}
|
||||||
|
@ -1,144 +0,0 @@
|
|||||||
import { unwrap } from "@snort/shared";
|
|
||||||
import {
|
|
||||||
decodeTLV,
|
|
||||||
encodeTLVEntries,
|
|
||||||
EventKind,
|
|
||||||
NostrEvent,
|
|
||||||
NostrPrefix,
|
|
||||||
RequestBuilder,
|
|
||||||
SystemInterface,
|
|
||||||
TaggedNostrEvent,
|
|
||||||
TLVEntryType,
|
|
||||||
UserMetadata,
|
|
||||||
} from "@snort/system";
|
|
||||||
|
|
||||||
import { Chat, ChatParticipant, ChatSystem, ChatType, lastReadInChat } from "@/chat";
|
|
||||||
import { findTag } from "@/Utils";
|
|
||||||
import { LoginSession } from "@/Utils/Login";
|
|
||||||
|
|
||||||
export class Nip28ChatSystem implements ChatSystem {
|
|
||||||
readonly ChannelKinds = [
|
|
||||||
EventKind.PublicChatChannel,
|
|
||||||
EventKind.PublicChatMessage,
|
|
||||||
EventKind.PublicChatMetadata,
|
|
||||||
EventKind.PublicChatMuteMessage,
|
|
||||||
EventKind.PublicChatMuteUser,
|
|
||||||
];
|
|
||||||
|
|
||||||
subscription(session: LoginSession): RequestBuilder {
|
|
||||||
const chats = (session.extraChats ?? []).filter(a => a.startsWith(NostrPrefix.Chat28));
|
|
||||||
const chatId = (v: string) => unwrap(decodeTLV(v).find(a => a.type === TLVEntryType.Special)).value as string;
|
|
||||||
|
|
||||||
const rb = new RequestBuilder(`nip28:${session.id}`);
|
|
||||||
if (chats.length > 0) {
|
|
||||||
rb.withFilter()
|
|
||||||
.ids(chats.map(v => chatId(v)))
|
|
||||||
.kinds([EventKind.PublicChatChannel, EventKind.PublicChatMetadata]);
|
|
||||||
for (const c of chats) {
|
|
||||||
const id = chatId(c);
|
|
||||||
rb.withFilter().tag("e", [id]).kinds(this.ChannelKinds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rb;
|
|
||||||
}
|
|
||||||
|
|
||||||
processEvents(): Promise<void> {
|
|
||||||
// nothing to do
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
listChats(pk: string, evs: Array<TaggedNostrEvent>): Chat[] {
|
|
||||||
const chats = this.#chatChannels(evs);
|
|
||||||
const ret = Object.entries(chats).map(([k, v]) => {
|
|
||||||
return Nip28ChatSystem.createChatObj(Nip28ChatSystem.chatId(k), v);
|
|
||||||
});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static chatId(id: string) {
|
|
||||||
return encodeTLVEntries(NostrPrefix.Chat28, {
|
|
||||||
type: TLVEntryType.Special,
|
|
||||||
value: id,
|
|
||||||
length: id.length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static createChatObj(id: string, messages: Array<NostrEvent>) {
|
|
||||||
const last = lastReadInChat(id);
|
|
||||||
const participants = decodeTLV(id)
|
|
||||||
.filter(v => v.type === TLVEntryType.Special)
|
|
||||||
.map(
|
|
||||||
v =>
|
|
||||||
({
|
|
||||||
type: "generic",
|
|
||||||
id: v.value as string,
|
|
||||||
profile: this.#chatProfileFromMessages(messages),
|
|
||||||
}) as ChatParticipant,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
type: ChatType.PublicGroupChat,
|
|
||||||
id,
|
|
||||||
unread: messages.reduce((acc, v) => (v.created_at > last ? acc++ : acc), 0),
|
|
||||||
lastMessage: messages.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0),
|
|
||||||
participants,
|
|
||||||
messages: messages
|
|
||||||
.filter(a => a.kind === EventKind.PublicChatMessage)
|
|
||||||
.map(m => ({
|
|
||||||
id: m.id,
|
|
||||||
created_at: m.created_at,
|
|
||||||
from: m.pubkey,
|
|
||||||
tags: m.tags,
|
|
||||||
content: m.content,
|
|
||||||
needsDecryption: false,
|
|
||||||
})),
|
|
||||||
createMessage: async (msg, pub) => {
|
|
||||||
return [
|
|
||||||
await pub.generic(eb => {
|
|
||||||
return eb.kind(EventKind.PublicChatMessage).content(msg).tag(["e", participants[0].id, "", "root"]);
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
sendMessage: (ev, system: SystemInterface) => {
|
|
||||||
ev.forEach(a => system.BroadcastEvent(a));
|
|
||||||
},
|
|
||||||
} as Chat;
|
|
||||||
}
|
|
||||||
|
|
||||||
static #chatProfileFromMessages(messages: Array<NostrEvent>) {
|
|
||||||
const chatDefs = messages.filter(
|
|
||||||
a => a.kind === EventKind.PublicChatChannel || a.kind === EventKind.PublicChatMetadata,
|
|
||||||
);
|
|
||||||
const chatDef =
|
|
||||||
chatDefs.length > 0
|
|
||||||
? chatDefs.reduce((acc, v) => (acc.created_at > v.created_at ? acc : v), chatDefs[0])
|
|
||||||
: undefined;
|
|
||||||
return chatDef ? (JSON.parse(chatDef.content) as UserMetadata) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
#chatChannels(evs: Array<TaggedNostrEvent>) {
|
|
||||||
const chats = evs.reduce(
|
|
||||||
(acc, v) => {
|
|
||||||
const k = this.#chatId(v);
|
|
||||||
if (k) {
|
|
||||||
acc[k] ??= [];
|
|
||||||
acc[k].push(v);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<string, Array<NostrEvent>>,
|
|
||||||
);
|
|
||||||
return chats;
|
|
||||||
}
|
|
||||||
|
|
||||||
#chatId(ev: NostrEvent) {
|
|
||||||
if (ev.kind === EventKind.PublicChatChannel) {
|
|
||||||
return ev.id;
|
|
||||||
} else if (ev.kind === EventKind.PublicChatMetadata) {
|
|
||||||
return findTag(ev, "e");
|
|
||||||
} else if (this.ChannelKinds.includes(ev.kind)) {
|
|
||||||
return ev.tags.find(a => a[0] === "e" && a[3] === "root")?.[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Nip28Chats = new Nip28ChatSystem();
|
|
@ -1,109 +0,0 @@
|
|||||||
import { dedupe, ExternalStore, FeedCache, removeUndefined } from "@snort/shared";
|
|
||||||
import { EventKind, NostrEvent, RequestBuilder, SystemInterface, TaggedNostrEvent } from "@snort/system";
|
|
||||||
|
|
||||||
import { Chat, ChatSystem, ChatType, lastReadInChat } from "@/chat";
|
|
||||||
import { LoginSession } from "@/Utils/Login";
|
|
||||||
|
|
||||||
export class Nip29ChatSystem extends ExternalStore<Array<Chat>> implements ChatSystem {
|
|
||||||
readonly #cache: FeedCache<NostrEvent>;
|
|
||||||
|
|
||||||
constructor(cache: FeedCache<NostrEvent>) {
|
|
||||||
super();
|
|
||||||
this.#cache = cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
processEvents(): Promise<void> {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
takeSnapshot(): Chat[] {
|
|
||||||
return this.listChats();
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription(session: LoginSession) {
|
|
||||||
const id = session.publicKey;
|
|
||||||
if (!id) return;
|
|
||||||
const gs = id.split("/", 2);
|
|
||||||
const rb = new RequestBuilder(`nip29:${id}`);
|
|
||||||
const last = this.listChats().find(a => a.id === id)?.lastMessage;
|
|
||||||
rb.withFilter()
|
|
||||||
.relay(`wss://${gs[0]}`)
|
|
||||||
.kinds([EventKind.SimpleChatMessage])
|
|
||||||
.tag("g", [`/${gs[1]}`])
|
|
||||||
.since(last);
|
|
||||||
rb.withFilter()
|
|
||||||
.relay(`wss://${gs[0]}`)
|
|
||||||
.kinds([EventKind.SimpleChatMetadata])
|
|
||||||
.tag("d", [`/${gs[1]}`]);
|
|
||||||
return rb;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onEvent(evs: readonly TaggedNostrEvent[]) {
|
|
||||||
const msg = evs.filter(a => a.kind === EventKind.SimpleChatMessage && a.tags.some(b => b[0] === "g"));
|
|
||||||
if (msg.length > 0) {
|
|
||||||
await this.#cache.bulkSet(msg);
|
|
||||||
this.notifyChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listChats(): Chat[] {
|
|
||||||
const allMessages = this.#nip29Chats();
|
|
||||||
const groups = dedupe(
|
|
||||||
removeUndefined(allMessages.map(a => a.tags.find(b => b[0] === "g"))).map(a => `${a[2]}${a[1]}`),
|
|
||||||
);
|
|
||||||
return groups.map(g => {
|
|
||||||
const [relay, channel] = g.split("/", 2);
|
|
||||||
const messages = allMessages.filter(
|
|
||||||
a => `${a.tags.find(b => b[0] === "g")?.[2]}${a.tags.find(b => b[0] === "g")?.[1]}` === g,
|
|
||||||
);
|
|
||||||
const lastRead = lastReadInChat(g);
|
|
||||||
return {
|
|
||||||
type: ChatType.PublicGroupChat,
|
|
||||||
id: g,
|
|
||||||
title: `${relay}/${channel}`,
|
|
||||||
unread: messages.reduce((acc, v) => (v.created_at > lastRead ? acc++ : acc), 0),
|
|
||||||
lastMessage: messages.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0),
|
|
||||||
messages: messages.map(m => ({
|
|
||||||
id: m.id,
|
|
||||||
created_at: m.created_at,
|
|
||||||
from: m.pubkey,
|
|
||||||
tags: m.tags,
|
|
||||||
needsDecryption: false,
|
|
||||||
content: m.content,
|
|
||||||
decrypt: async () => {
|
|
||||||
return m.content;
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
participants: [
|
|
||||||
{
|
|
||||||
type: "generic",
|
|
||||||
id: "",
|
|
||||||
profile: {
|
|
||||||
name: `${relay}/${channel}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
createMessage: async (msg, pub) => {
|
|
||||||
return [
|
|
||||||
await pub.generic(eb => {
|
|
||||||
return eb
|
|
||||||
.kind(EventKind.SimpleChatMessage)
|
|
||||||
.tag(["g", `/${channel}`, relay])
|
|
||||||
.content(msg);
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
sendMessage: async (ev, system: SystemInterface) => {
|
|
||||||
ev.forEach(async a => {
|
|
||||||
system.HandleEvent("*", { ...a, relays: [] });
|
|
||||||
await system.WriteOnceToRelay(`wss://${relay}`, a);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
} as Chat;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#nip29Chats() {
|
|
||||||
return this.#cache.snapshot().filter(a => a.kind === EventKind.SimpleChatMessage);
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,7 +14,6 @@ export enum NostrPrefix {
|
|||||||
Address = "naddr",
|
Address = "naddr",
|
||||||
Req = "nreq",
|
Req = "nreq",
|
||||||
Chat17 = "nchat17",
|
Chat17 = "nchat17",
|
||||||
Chat28 = "nchat28",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TLVEntryType {
|
export enum TLVEntryType {
|
||||||
|
Reference in New Issue
Block a user