mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
add invitation event to DirectedMessageController (#231)
This commit is contained in:
parent
9b8d9aa47d
commit
eab9d7fed6
15
UI/app.tsx
15
UI/app.tsx
@ -139,7 +139,7 @@ export class App {
|
||||
const conversationLists = new ConversationLists(args.ctx, profileSyncer);
|
||||
conversationLists.addEvents(args.database.events);
|
||||
|
||||
const dmControl = new DirectedMessageController(args.ctx);
|
||||
const dmController = new DirectedMessageController(args.ctx);
|
||||
|
||||
const groupSyncer = new GroupChatSyncer(args.database, args.pool);
|
||||
const groupChatController = new GroupMessageController(
|
||||
@ -152,15 +152,22 @@ export class App {
|
||||
(async () => {
|
||||
for (const e of args.database.events) {
|
||||
if (e.kind == NostrKind.Group_Message) {
|
||||
const err = await groupChatController.addEvent({
|
||||
let err = await groupChatController.addEvent({
|
||||
...e,
|
||||
kind: e.kind,
|
||||
});
|
||||
if (err instanceof Error) {
|
||||
console.error(err.message);
|
||||
}
|
||||
err = await dmController.addEvent({
|
||||
...e,
|
||||
kind: e.kind,
|
||||
});
|
||||
if (err instanceof Error) {
|
||||
console.error(err);
|
||||
}
|
||||
} else if (e.kind == NostrKind.DIRECT_MESSAGE) {
|
||||
const error = await dmControl.addEvent({
|
||||
const error = await dmController.addEvent({
|
||||
...e,
|
||||
kind: e.kind,
|
||||
});
|
||||
@ -190,7 +197,7 @@ export class App {
|
||||
relayConfig,
|
||||
groupChatController,
|
||||
lamport,
|
||||
dmControl,
|
||||
dmController,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,10 @@ import { ConversationLists } from "./conversation-list.ts";
|
||||
import * as csp from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
||||
import { Database_Contextual_View } from "../database.ts";
|
||||
|
||||
import { convertEventsToChatMessages, DirectedMessageController, sendDMandImages } from "../features/dm.ts";
|
||||
import { DirectedMessageController, sendDMandImages } from "../features/dm.ts";
|
||||
import { notify } from "./notification.ts";
|
||||
import { EventBus } from "../event-bus.ts";
|
||||
import { ContactUpdate } from "./conversation-list.tsx";
|
||||
import { ContactUpdate, IsGruopChatSupported } from "./conversation-list.tsx";
|
||||
import { MyProfileUpdate } from "./edit-profile.tsx";
|
||||
import { EditorEvent, EditorModel, new_DM_EditorModel, SendMessage } from "./editor.tsx";
|
||||
import { DirectMessagePanelUpdate } from "./message-panel.tsx";
|
||||
@ -569,7 +569,14 @@ export async function* Database_Update(
|
||||
}
|
||||
}
|
||||
} else if (e.kind == NostrKind.Group_Message) {
|
||||
const err = await groupController.addEvent({
|
||||
let err = await groupController.addEvent({
|
||||
...e,
|
||||
kind: e.kind,
|
||||
});
|
||||
if (err instanceof Error) {
|
||||
console.error(err);
|
||||
}
|
||||
err = await dmController.addEvent({
|
||||
...e,
|
||||
kind: e.kind,
|
||||
});
|
||||
@ -663,19 +670,19 @@ export async function handle_SendMessage(
|
||||
}
|
||||
} else {
|
||||
// todo: hack, change later
|
||||
// const invitation = isInvitation(event.text);
|
||||
// if (invitation) {
|
||||
// const invitationEvent = await groupControl.createInvitation(invitation, event.pubkey);
|
||||
// if (invitationEvent instanceof Error) {
|
||||
// return invitationEvent;
|
||||
// }
|
||||
// console.log(invitationEvent);
|
||||
// const err = await pool.sendEvent(invitationEvent);
|
||||
// if (err instanceof Error) {
|
||||
// return err;
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
const invitation = isInvitation(event.text);
|
||||
if (invitation && IsGruopChatSupported) {
|
||||
const invitationEvent = await groupControl.createInvitation(invitation, event.pubkey);
|
||||
if (invitationEvent instanceof Error) {
|
||||
return invitationEvent;
|
||||
}
|
||||
console.log(invitationEvent);
|
||||
const err = await pool.sendEvent(invitationEvent);
|
||||
if (err instanceof Error) {
|
||||
return err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const events = await sendDMandImages({
|
||||
sender: ctx,
|
||||
receiverPublicKey: event.pubkey,
|
||||
|
@ -3,7 +3,7 @@ import { PublicKey } from "../lib/nostr-ts/key.ts";
|
||||
import { NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
|
||||
import { getTags, Parsed_Event } from "../nostr.ts";
|
||||
import { ProfileSyncer } from "../features/profile.ts";
|
||||
import { GroupChatCreation } from "../features/gm.ts";
|
||||
import { gm_Creation } from "../features/gm.ts";
|
||||
|
||||
export interface ConversationSummary {
|
||||
pubkey: PublicKey;
|
||||
@ -77,7 +77,7 @@ export class ConversationLists implements ConversationListRetriever, NewMessageC
|
||||
}
|
||||
}
|
||||
|
||||
addGroupCreation(groupChatCreation: GroupChatCreation) {
|
||||
addGroupCreation(groupChatCreation: gm_Creation) {
|
||||
const publicKey = groupChatCreation.groupKey.publicKey;
|
||||
this.groupChatSummaries.set(publicKey.hex, {
|
||||
pubkey: publicKey,
|
||||
|
@ -19,7 +19,7 @@ import { UI_Interaction_Event } from "./app_update.tsx";
|
||||
import { ProfileData } from "../features/profile.ts";
|
||||
import { ProfileGetter } from "./search.tsx";
|
||||
|
||||
const IsGruopChatSupported = false;
|
||||
export const IsGruopChatSupported = false;
|
||||
|
||||
export interface ConversationListRetriever {
|
||||
getContacts: () => Iterable<ConversationSummary>;
|
||||
|
@ -19,7 +19,6 @@ import { GroupMessageController } from "../features/gm.ts";
|
||||
import { ProfileGetter } from "./search.tsx";
|
||||
import { InviteIcon } from "./icons2/invite-icon.tsx";
|
||||
import { PublicKey } from "../lib/nostr-ts/key.ts";
|
||||
import { convertEventsToChatMessages } from "../features/dm.ts";
|
||||
import { ChatMessage } from "./message.ts";
|
||||
import { EditorModel } from "./editor.tsx";
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { IconButtonClass } from "./components/tw.ts";
|
||||
import { sleep } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
||||
import { emitFunc } from "../event-bus.ts";
|
||||
|
||||
import { ChatMessage, groupContinuousMessages, sortMessage, urlIsImage } from "./message.ts";
|
||||
import { ChatMessage, groupContinuousMessages, parseContent, sortMessage, urlIsImage } from "./message.ts";
|
||||
import { PublicKey } from "../lib/nostr-ts/key.ts";
|
||||
import { NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
|
||||
import { Parsed_Event, PinConversation, Profile_Nostr_Event, UnpinConversation } from "../nostr.ts";
|
||||
@ -119,7 +119,7 @@ export class MessagePanel extends Component<DirectMessagePanelProps> {
|
||||
|
||||
<MessageList
|
||||
myPublicKey={props.myPublicKey}
|
||||
threads={props.messages}
|
||||
messages={props.messages}
|
||||
emit={props.emit}
|
||||
profilesSyncer={props.profilesSyncer}
|
||||
eventSyncer={props.eventSyncer}
|
||||
@ -166,7 +166,7 @@ export class MessagePanel extends Component<DirectMessagePanelProps> {
|
||||
}
|
||||
interface MessageListProps {
|
||||
myPublicKey: PublicKey;
|
||||
threads: ChatMessage[];
|
||||
messages: ChatMessage[];
|
||||
emit: emitFunc<DirectMessagePanelUpdate>;
|
||||
profilesSyncer: ProfileSyncer;
|
||||
eventSyncer: EventSyncer;
|
||||
@ -200,20 +200,20 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
|
||||
e.currentTarget.scrollTop < 1000
|
||||
) {
|
||||
const ok = await this.jitter.shouldExecute();
|
||||
if (!ok || this.state.currentRenderCount >= this.props.threads.length) {
|
||||
if (!ok || this.state.currentRenderCount >= this.props.messages.length) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
currentRenderCount: Math.min(
|
||||
this.state.currentRenderCount + ItemsOfPerPage,
|
||||
this.props.threads.length,
|
||||
this.props.messages.length,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
sortAndSliceMessage = () => {
|
||||
return sortMessage(this.props.threads)
|
||||
return sortMessage(this.props.messages)
|
||||
.slice(
|
||||
0,
|
||||
this.state.currentRenderCount,
|
||||
@ -239,6 +239,7 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
|
||||
profilesSyncer: this.props.profilesSyncer,
|
||||
eventSyncer: this.props.eventSyncer,
|
||||
authorProfile: profileEvent ? profileEvent.profile : undefined,
|
||||
profileGetter: this.props.profileGetter,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -291,6 +292,7 @@ function MessageBoxGroup(props: {
|
||||
emit: emitFunc<DirectMessagePanelUpdate | ViewUserDetail>;
|
||||
profilesSyncer: ProfileSyncer;
|
||||
eventSyncer: EventSyncer;
|
||||
profileGetter: ProfileGetter;
|
||||
}) {
|
||||
const messageGroups = props.messages.reverse();
|
||||
if (messageGroups.length == 0) {
|
||||
@ -335,6 +337,7 @@ function MessageBoxGroup(props: {
|
||||
props.profilesSyncer,
|
||||
props.eventSyncer,
|
||||
props.emit,
|
||||
props.profileGetter,
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
@ -364,6 +367,7 @@ function MessageBoxGroup(props: {
|
||||
props.profilesSyncer,
|
||||
props.eventSyncer,
|
||||
props.emit,
|
||||
props.profileGetter
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
@ -455,18 +459,22 @@ export function ParseMessageContent(
|
||||
profilesSyncer: ProfileSyncer,
|
||||
eventSyncer: EventSyncer,
|
||||
emit: emitFunc<ViewUserDetail | ViewThread | ViewNoteThread>,
|
||||
profileGetter: ProfileGetter,
|
||||
) {
|
||||
if (message.type == "image") {
|
||||
return <img src={message.content} />;
|
||||
}
|
||||
|
||||
let parsedContentItems;
|
||||
if (message.event.kind == NostrKind.Group_Message) {
|
||||
return <p>{message.content}</p>;
|
||||
parsedContentItems = parseContent(message.content);
|
||||
} else {
|
||||
parsedContentItems = message.event.parsedContentItems;
|
||||
}
|
||||
|
||||
const vnode = [];
|
||||
let start = 0;
|
||||
for (const item of message.event.parsedContentItems) {
|
||||
for (const item of parsedContentItems) {
|
||||
vnode.push(message.content.slice(start, item.start));
|
||||
const itemStr = message.content.slice(item.start, item.end + 1);
|
||||
switch (item.type) {
|
||||
@ -486,9 +494,10 @@ export function ParseMessageContent(
|
||||
case "npub":
|
||||
{
|
||||
if (authorProfile) {
|
||||
const profile = profileGetter.getProfilesByPublicKey(item.pubkey);
|
||||
vnode.push(
|
||||
<ProfileCard
|
||||
profileData={authorProfile}
|
||||
profileData={profile ? profile.profile : undefined}
|
||||
publicKey={item.pubkey}
|
||||
emit={emit}
|
||||
/>,
|
||||
@ -511,7 +520,8 @@ export function ParseMessageContent(
|
||||
vnode.push(itemStr);
|
||||
break;
|
||||
}
|
||||
vnode.push(Card(event, authorProfile, emit));
|
||||
const profile = profileGetter.getProfilesByPublicKey(event.publicKey);
|
||||
vnode.push(Card(event, profile ? profile.profile : undefined, emit));
|
||||
}
|
||||
break;
|
||||
case "tag":
|
||||
|
@ -225,6 +225,18 @@ Deno.test("inline parse", async (t) => {
|
||||
end: 23,
|
||||
}],
|
||||
},
|
||||
{
|
||||
input:
|
||||
"You have been invited to group npub1k9p03z0gqsz2dqvjrkp6337lq5tl9nzj4wx0sfrpjmje2ze8nyls424ds3",
|
||||
output: [{
|
||||
type: "npub",
|
||||
pubkey: PublicKey.FromBech32(
|
||||
"npub1k9p03z0gqsz2dqvjrkp6337lq5tl9nzj4wx0sfrpjmje2ze8nyls424ds3",
|
||||
),
|
||||
start: 31,
|
||||
end: 93,
|
||||
}],
|
||||
},
|
||||
];
|
||||
for (const [i, test] of data.entries()) {
|
||||
await t.step(test.input, () => {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { PublicKey } from "../lib/nostr-ts/key.ts";
|
||||
import { DirectedMessage_Event } from "../nostr.ts";
|
||||
import { DirectedMessage_Event, Parsed_Event } from "../nostr.ts";
|
||||
import { Nevent, NostrAddress, NostrProfile, NoteID } from "../lib/nostr-ts/nip19.ts";
|
||||
import { NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
|
||||
import { NostrKind } from "../lib/nostr-ts/nostr.ts";
|
||||
import { gm_Invitation } from "../features/gm.ts";
|
||||
|
||||
export function* parseContent(content: string) {
|
||||
// URLs
|
||||
@ -164,14 +165,22 @@ export type ContentItem = {
|
||||
};
|
||||
|
||||
// Think of ChatMessage as an materialized view of NostrEvent
|
||||
export interface ChatMessage {
|
||||
readonly event: DirectedMessage_Event | NostrEvent<NostrKind.Group_Message>;
|
||||
readonly author: PublicKey;
|
||||
export type ChatMessage = {
|
||||
readonly type: "image" | "text";
|
||||
readonly event: DirectedMessage_Event | Parsed_Event<NostrKind.Group_Message>;
|
||||
readonly author: PublicKey;
|
||||
readonly created_at: Date;
|
||||
readonly lamport: number | undefined;
|
||||
readonly content: string;
|
||||
}
|
||||
} | {
|
||||
readonly type: "gm_invitation";
|
||||
readonly event: Parsed_Event<NostrKind.Group_Message>;
|
||||
readonly invitation: gm_Invitation;
|
||||
readonly author: PublicKey;
|
||||
readonly created_at: Date;
|
||||
readonly lamport: number | undefined;
|
||||
readonly content: string;
|
||||
};
|
||||
|
||||
export function urlIsImage(url: string) {
|
||||
const trimmed = url.trim();
|
||||
|
141
features/dm.ts
141
features/dm.ts
@ -16,6 +16,7 @@ import { prepareEncryptedNostrEvent } from "../lib/nostr-ts/event.ts";
|
||||
import { DirectMessageGetter } from "../UI/app_update.tsx";
|
||||
import { parseDM } from "../database.ts";
|
||||
import { ChatMessage } from "../UI/message.ts";
|
||||
import { decodeInvitation, gmEventType } from "./gm.ts";
|
||||
|
||||
export async function sendDMandImages(args: {
|
||||
sender: NostrAccountContext;
|
||||
@ -162,31 +163,84 @@ export class DirectedMessageController implements DirectMessageGetter {
|
||||
public readonly ctx: NostrAccountContext,
|
||||
) {}
|
||||
|
||||
public readonly directed_messages = new Map<string, DirectedMessage_Event>();
|
||||
public readonly directed_messages = new Map<string, ChatMessage>();
|
||||
|
||||
// get the direct messages between me and this pubkey
|
||||
public getDirectMessages(pubkey: string) {
|
||||
const events = [];
|
||||
for (const event of this.directed_messages.values()) {
|
||||
if (is_DM_between(event, this.ctx.publicKey.hex, pubkey)) {
|
||||
events.push(event);
|
||||
public getDirectMessages(pubkey: string): ChatMessage[] {
|
||||
const messages = [];
|
||||
for (const message of this.directed_messages.values()) {
|
||||
if (is_DM_between(message.event, this.ctx.publicKey.hex, pubkey)) {
|
||||
if (message.event.kind == NostrKind.Group_Message) {
|
||||
console.log(message);
|
||||
}
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
const messages = convertEventsToChatMessages(events.sort(compare));
|
||||
messages.sort((a, b) => compare(a.event, b.event));
|
||||
return messages;
|
||||
}
|
||||
|
||||
async addEvent(event: Parsed_Event<NostrKind.DIRECT_MESSAGE>) {
|
||||
const dmEvent = await parseDM(
|
||||
event,
|
||||
this.ctx,
|
||||
event.parsedTags,
|
||||
event.publicKey,
|
||||
);
|
||||
if (dmEvent instanceof Error) {
|
||||
return dmEvent;
|
||||
async addEvent(event: Parsed_Event<NostrKind.DIRECT_MESSAGE | NostrKind.Group_Message>) {
|
||||
const kind = event.kind;
|
||||
if (kind == NostrKind.Group_Message) {
|
||||
console.log("dm add event", kind);
|
||||
const gmEvent = { ...event, kind };
|
||||
const type = gmEventType(this.ctx, gmEvent);
|
||||
if (type == "gm_invitation") {
|
||||
const invitation = await decodeInvitation(this.ctx, gmEvent);
|
||||
if (invitation instanceof Error) {
|
||||
return invitation;
|
||||
}
|
||||
console.log("dm add event", invitation);
|
||||
this.directed_messages.set(gmEvent.id, {
|
||||
type: "text", // todo: change to invitation
|
||||
event: gmEvent,
|
||||
author: gmEvent.publicKey,
|
||||
content: `You have been invited to group ${invitation.groupAddr.bech32()}`,
|
||||
created_at: new Date(gmEvent.created_at * 1000),
|
||||
// invitation: invitation,
|
||||
lamport: gmEvent.parsedTags.lamport_timestamp,
|
||||
});
|
||||
}
|
||||
// else ignore
|
||||
} else {
|
||||
const dmEvent = await parseDM(
|
||||
{
|
||||
...event,
|
||||
kind,
|
||||
},
|
||||
this.ctx,
|
||||
event.parsedTags,
|
||||
event.publicKey,
|
||||
);
|
||||
if (dmEvent instanceof Error) {
|
||||
return dmEvent;
|
||||
}
|
||||
const isImage = dmEvent.parsedTags.image;
|
||||
if (isImage) {
|
||||
const imageBase64 = reassembleBase64ImageFromEvents([dmEvent]);
|
||||
if (imageBase64 instanceof Error) {
|
||||
return imageBase64;
|
||||
}
|
||||
this.directed_messages.set(event.id, {
|
||||
event: dmEvent,
|
||||
author: dmEvent.publicKey,
|
||||
content: imageBase64,
|
||||
type: "image",
|
||||
created_at: new Date(dmEvent.created_at * 1000),
|
||||
lamport: dmEvent.parsedTags.lamport_timestamp,
|
||||
});
|
||||
} else {
|
||||
this.directed_messages.set(event.id, {
|
||||
event: dmEvent,
|
||||
author: dmEvent.publicKey,
|
||||
content: dmEvent.decryptedContent,
|
||||
type: "text",
|
||||
created_at: new Date(dmEvent.created_at * 1000),
|
||||
lamport: dmEvent.parsedTags.lamport_timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.directed_messages.set(event.id, dmEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,56 +253,3 @@ function is_DM_between(event: NostrEvent, myPubkey: string, theirPubKey: string)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function convertEventsToChatMessages(
|
||||
events: Iterable<DirectedMessage_Event>,
|
||||
): ChatMessage[] {
|
||||
const messages: ChatMessage[] = [];
|
||||
const groups = groupImageEvents(events);
|
||||
let pubKeys = Array.from(groups.values()).map((es) => es[0].pubkey);
|
||||
|
||||
let textEvents = groups.get(undefined);
|
||||
if (textEvents === undefined) {
|
||||
textEvents = [];
|
||||
}
|
||||
pubKeys = pubKeys.concat(textEvents.map((e) => e.pubkey));
|
||||
|
||||
groups.delete(undefined);
|
||||
|
||||
for (let i = 0; i < textEvents.length; i++) {
|
||||
const pubkey = PublicKey.FromHex(textEvents[i].pubkey);
|
||||
if (pubkey instanceof Error) {
|
||||
throw new Error(textEvents[i].pubkey);
|
||||
}
|
||||
messages.push({
|
||||
event: textEvents[i],
|
||||
author: pubkey,
|
||||
content: textEvents[i].decryptedContent,
|
||||
type: "text",
|
||||
created_at: new Date(textEvents[i].created_at * 1000),
|
||||
lamport: getTags(textEvents[i]).lamport_timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
for (const imageEvents of groups.values()) {
|
||||
const imageBase64 = reassembleBase64ImageFromEvents(imageEvents);
|
||||
if (imageBase64 instanceof Error) {
|
||||
console.info(imageBase64.message);
|
||||
continue;
|
||||
}
|
||||
const pubkey = PublicKey.FromHex(imageEvents[0].pubkey);
|
||||
if (pubkey instanceof Error) {
|
||||
throw new Error(imageEvents[0].pubkey);
|
||||
}
|
||||
messages.push({
|
||||
event: imageEvents[0],
|
||||
author: pubkey,
|
||||
content: imageBase64,
|
||||
type: "image",
|
||||
created_at: new Date(imageEvents[0].created_at * 1000),
|
||||
lamport: getTags(imageEvents[0]).lamport_timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
132
features/gm.ts
132
features/gm.ts
@ -9,7 +9,7 @@ import { prepareEncryptedNostrEvent } from "../lib/nostr-ts/event.ts";
|
||||
import { PrivateKey, PublicKey } from "../lib/nostr-ts/key.ts";
|
||||
import { InMemoryAccountContext, NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
|
||||
import { ConnectionPool } from "../lib/nostr-ts/relay.ts";
|
||||
import { getTags } from "../nostr.ts";
|
||||
import { getTags, Parsed_Event } from "../nostr.ts";
|
||||
import { parseJSON, ProfileSyncer } from "./profile.ts";
|
||||
|
||||
export type GM_Types = "gm_creation" | "gm_message" | "gm_invitation";
|
||||
@ -18,19 +18,19 @@ export type GroupMessage = {
|
||||
event: NostrEvent<NostrKind.Group_Message>;
|
||||
};
|
||||
|
||||
export type GroupChatCreation = {
|
||||
export type gm_Creation = {
|
||||
cipherKey: InMemoryAccountContext;
|
||||
groupKey: InMemoryAccountContext;
|
||||
};
|
||||
|
||||
export type GroupChatInvitation = {
|
||||
export type gm_Invitation = {
|
||||
cipherKey: InMemoryAccountContext;
|
||||
groupAddr: PublicKey;
|
||||
};
|
||||
|
||||
export class GroupMessageController implements GroupMessageGetter, GroupMessageListGetter {
|
||||
created_groups = new Map<string, GroupChatCreation>();
|
||||
invitations = new Map<string, GroupChatInvitation>();
|
||||
created_groups = new Map<string, gm_Creation>();
|
||||
invitations = new Map<string, gm_Invitation>();
|
||||
messages = new Map<string, ChatMessage[]>();
|
||||
resync_chan = new Channel<null>();
|
||||
|
||||
@ -65,7 +65,7 @@ export class GroupMessageController implements GroupMessageGetter, GroupMessageL
|
||||
return msgs ? msgs : [];
|
||||
}
|
||||
|
||||
async encodeCreationToNostrEvent(groupCreation: GroupChatCreation) {
|
||||
async encodeCreationToNostrEvent(groupCreation: gm_Creation) {
|
||||
const event = prepareEncryptedNostrEvent(this.ctx, {
|
||||
encryptKey: this.ctx.publicKey,
|
||||
kind: NostrKind.Group_Message,
|
||||
@ -80,7 +80,7 @@ export class GroupMessageController implements GroupMessageGetter, GroupMessageL
|
||||
}
|
||||
|
||||
createGroupChat() {
|
||||
const groupChatCreation: GroupChatCreation = {
|
||||
const groupChatCreation: gm_Creation = {
|
||||
cipherKey: InMemoryAccountContext.New(PrivateKey.Generate()),
|
||||
groupKey: InMemoryAccountContext.New(PrivateKey.Generate()),
|
||||
};
|
||||
@ -90,12 +90,8 @@ export class GroupMessageController implements GroupMessageGetter, GroupMessageL
|
||||
return groupChatCreation;
|
||||
}
|
||||
|
||||
async addEvent(event: NostrEvent<NostrKind.Group_Message>) {
|
||||
const type = await eventType(this.ctx, event);
|
||||
if (type instanceof Error) {
|
||||
return type;
|
||||
}
|
||||
|
||||
async addEvent(event: Parsed_Event<NostrKind.Group_Message>) {
|
||||
const type = gmEventType(this.ctx, event);
|
||||
if (type == "gm_creation") {
|
||||
return await this.handleCreation(event);
|
||||
} else if (type == "gm_message") {
|
||||
@ -107,7 +103,7 @@ export class GroupMessageController implements GroupMessageGetter, GroupMessageL
|
||||
}
|
||||
}
|
||||
|
||||
async addEvents(...events: NostrEvent<NostrKind.Group_Message>[]) {
|
||||
async addEvents(...events: Parsed_Event<NostrKind.Group_Message>[]) {
|
||||
for (const e of events) {
|
||||
const err = await this.addEvent(e);
|
||||
if (err instanceof Error) {
|
||||
@ -117,55 +113,16 @@ export class GroupMessageController implements GroupMessageGetter, GroupMessageL
|
||||
}
|
||||
|
||||
async handleInvitation(event: NostrEvent<NostrKind.Group_Message>) {
|
||||
const decryptedContent = await this.ctx.decrypt(event.pubkey, event.content);
|
||||
if (decryptedContent instanceof Error) {
|
||||
return decryptedContent;
|
||||
const invitation = await decodeInvitation(this.ctx, event);
|
||||
if (invitation instanceof Error) {
|
||||
return invitation;
|
||||
}
|
||||
|
||||
const json = parseJSON<unknown>(decryptedContent);
|
||||
if (json instanceof Error) {
|
||||
return json;
|
||||
}
|
||||
|
||||
const author = PublicKey.FromHex(event.pubkey);
|
||||
if (author instanceof Error) {
|
||||
return author;
|
||||
}
|
||||
|
||||
let message: {
|
||||
type: string;
|
||||
cipherKey: string;
|
||||
groupAddr: string;
|
||||
};
|
||||
try {
|
||||
message = z.object({
|
||||
type: z.string(),
|
||||
cipherKey: z.string(),
|
||||
groupAddr: z.string(),
|
||||
}).parse(json);
|
||||
} catch (e) {
|
||||
return e as Error;
|
||||
}
|
||||
|
||||
// add invitations
|
||||
const cipherKey = PrivateKey.FromBech32(message.cipherKey);
|
||||
if (cipherKey instanceof Error) {
|
||||
return cipherKey;
|
||||
}
|
||||
const groupAddr = PublicKey.FromBech32(message.groupAddr);
|
||||
if (groupAddr instanceof Error) {
|
||||
return groupAddr;
|
||||
}
|
||||
const invitation: GroupChatInvitation = {
|
||||
cipherKey: InMemoryAccountContext.New(cipherKey),
|
||||
groupAddr,
|
||||
};
|
||||
this.invitations.set(groupAddr.bech32(), invitation);
|
||||
this.groupSyncer.add(groupAddr.hex);
|
||||
this.profileSyncer.add(groupAddr.hex);
|
||||
this.invitations.set(invitation.groupAddr.bech32(), invitation);
|
||||
this.groupSyncer.add(invitation.groupAddr.hex);
|
||||
this.profileSyncer.add(invitation.groupAddr.hex);
|
||||
}
|
||||
|
||||
async handleMessage(event: NostrEvent<NostrKind.Group_Message>) {
|
||||
async handleMessage(event: Parsed_Event<NostrKind.Group_Message>) {
|
||||
const groupAddr = getTags(event).p[0];
|
||||
const groupAddrPubkey = PublicKey.FromHex(groupAddr);
|
||||
if (groupAddrPubkey instanceof Error) {
|
||||
@ -206,12 +163,12 @@ export class GroupMessageController implements GroupMessageGetter, GroupMessageL
|
||||
}
|
||||
|
||||
const chatMessage: ChatMessage = {
|
||||
type: "text",
|
||||
event: event,
|
||||
author: author,
|
||||
content: message.text,
|
||||
created_at: new Date(event.created_at * 1000),
|
||||
lamport: getTags(event).lamport_timestamp,
|
||||
type: "text",
|
||||
lamport: event.parsedTags.lamport_timestamp,
|
||||
};
|
||||
|
||||
const messages = this.messages.get(groupAddr);
|
||||
@ -319,10 +276,10 @@ function isCreation(event: NostrEvent<NostrKind.Group_Message>) {
|
||||
return event.tags.length == 0;
|
||||
}
|
||||
|
||||
async function eventType(
|
||||
export function gmEventType(
|
||||
ctx: NostrAccountContext,
|
||||
event: NostrEvent<NostrKind.Group_Message>,
|
||||
): Promise<GM_Types | Error> {
|
||||
): GM_Types {
|
||||
if (isCreation(event)) {
|
||||
return "gm_creation";
|
||||
}
|
||||
@ -371,3 +328,50 @@ export class GroupChatSyncer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function decodeInvitation(ctx: NostrAccountContext, event: NostrEvent<NostrKind.Group_Message>) {
|
||||
const decryptedContent = await ctx.decrypt(event.pubkey, event.content);
|
||||
if (decryptedContent instanceof Error) {
|
||||
return decryptedContent;
|
||||
}
|
||||
|
||||
const json = parseJSON<unknown>(decryptedContent);
|
||||
if (json instanceof Error) {
|
||||
return json;
|
||||
}
|
||||
|
||||
const author = PublicKey.FromHex(event.pubkey);
|
||||
if (author instanceof Error) {
|
||||
return author;
|
||||
}
|
||||
|
||||
let message: {
|
||||
type: string;
|
||||
cipherKey: string;
|
||||
groupAddr: string;
|
||||
};
|
||||
try {
|
||||
message = z.object({
|
||||
type: z.string(),
|
||||
cipherKey: z.string(),
|
||||
groupAddr: z.string(),
|
||||
}).parse(json);
|
||||
} catch (e) {
|
||||
return e as Error;
|
||||
}
|
||||
|
||||
// add invitations
|
||||
const cipherKey = PrivateKey.FromBech32(message.cipherKey);
|
||||
if (cipherKey instanceof Error) {
|
||||
return cipherKey;
|
||||
}
|
||||
const groupAddr = PublicKey.FromBech32(message.groupAddr);
|
||||
if (groupAddr instanceof Error) {
|
||||
return groupAddr;
|
||||
}
|
||||
const invitation: gm_Invitation = {
|
||||
cipherKey: InMemoryAccountContext.New(cipherKey),
|
||||
groupAddr,
|
||||
};
|
||||
return invitation;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user