From 03185c654b011bf8d947dd13c6447580f256b702 Mon Sep 17 00:00:00 2001 From: Airtune <84182637+Airtune@users.noreply.github.com> Date: Mon, 31 Jul 2023 07:29:45 -0400 Subject: [PATCH] Create nip28.ts and nip28.test.ts (#264) --- nip28.test.ts | 134 +++++++++++++++++++++++++++++++++++++++++ nip28.ts | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 nip28.test.ts create mode 100644 nip28.ts diff --git a/nip28.test.ts b/nip28.test.ts new file mode 100644 index 0000000..a850371 --- /dev/null +++ b/nip28.test.ts @@ -0,0 +1,134 @@ +import {Kind} from './event.ts' +import {getPublicKey} from './keys.ts' +import { + channelCreateEvent, + channelMetadataEvent, + channelMessageEvent, + channelHideMessageEvent, + channelMuteUserEvent, + ChannelMetadata, + ChannelMessageEventTemplate +} from './nip28.ts' + +const privateKey = + 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' +const publicKey = getPublicKey(privateKey) + +describe('NIP-28 Functions', () => { + const channelMetadata: ChannelMetadata = { + name: 'Test Channel', + about: 'This is a test channel', + picture: 'https://example.com/picture.jpg' + } + + it('channelCreateEvent should create an event with given template', () => { + const template = { + content: channelMetadata, + created_at: 1617932115 + } + + const event = channelCreateEvent(template, privateKey) + expect(event!.kind).toEqual(Kind.ChannelCreation) + expect(event!.content).toEqual(JSON.stringify(template.content)) + expect(event!.pubkey).toEqual(publicKey) + }) + + it('channelMetadataEvent should create a signed event with given template', () => { + const template = { + channel_create_event_id: 'channel creation event id', + content: channelMetadata, + created_at: 1617932115 + } + + const event = channelMetadataEvent(template, privateKey) + expect(event!.kind).toEqual(Kind.ChannelMetadata) + expect(event!.tags).toEqual([['e', template.channel_create_event_id]]) + expect(event!.content).toEqual(JSON.stringify(template.content)) + expect(event!.pubkey).toEqual(publicKey) + expect(typeof event!.id).toEqual('string') + expect(typeof event!.sig).toEqual('string') + }) + + it('channelMessageEvent should create a signed message event with given template', () => { + const template = { + channel_create_event_id: 'channel creation event id', + relay_url: 'https://relay.example.com', + content: 'Hello, world!', + created_at: 1617932115 + } + + const event = channelMessageEvent(template, privateKey) + expect(event.kind).toEqual(Kind.ChannelMessage) + expect(event.tags[0]).toEqual([ + 'e', + template.channel_create_event_id, + template.relay_url, + 'root' + ]) + expect(event.content).toEqual(template.content) + expect(event.pubkey).toEqual(publicKey) + expect(typeof event.id).toEqual('string') + expect(typeof event.sig).toEqual('string') + }) + + it('channelMessageEvent should create a signed message reply event with given template', () => { + const template: ChannelMessageEventTemplate = { + channel_create_event_id: 'channel creation event id', + reply_to_channel_message_event_id: 'channel message event id', + relay_url: 'https://relay.example.com', + content: 'Hello, world!', + created_at: 1617932115 + } + + const event = channelMessageEvent(template, privateKey) + expect(event.kind).toEqual(Kind.ChannelMessage) + expect(event.tags).toContainEqual([ + 'e', + template.channel_create_event_id, + template.relay_url, + 'root' + ]) + expect(event.tags).toContainEqual([ + 'e', + template.reply_to_channel_message_event_id, + template.relay_url, + 'reply' + ]) + expect(event.content).toEqual(template.content) + expect(event.pubkey).toEqual(publicKey) + expect(typeof event.id).toEqual('string') + expect(typeof event.sig).toEqual('string') + }) + + it('channelHideMessageEvent should create a signed event with given template', () => { + const template = { + channel_message_event_id: 'channel message event id', + content: {reason: 'Inappropriate content'}, + created_at: 1617932115 + } + + const event = channelHideMessageEvent(template, privateKey) + expect(event!.kind).toEqual(Kind.ChannelHideMessage) + expect(event!.tags).toEqual([['e', template.channel_message_event_id]]) + expect(event!.content).toEqual(JSON.stringify(template.content)) + expect(event!.pubkey).toEqual(publicKey) + expect(typeof event!.id).toEqual('string') + expect(typeof event!.sig).toEqual('string') + }) + + it('channelMuteUserEvent should create a signed event with given template', () => { + const template = { + content: {reason: 'Spamming'}, + created_at: 1617932115, + pubkey_to_mute: 'pubkey to mute' + } + + const event = channelMuteUserEvent(template, privateKey) + expect(event!.kind).toEqual(Kind.ChannelMuteUser) + expect(event!.tags).toEqual([['p', template.pubkey_to_mute]]) + expect(event!.content).toEqual(JSON.stringify(template.content)) + expect(event!.pubkey).toEqual(publicKey) + expect(typeof event!.id).toEqual('string') + expect(typeof event!.sig).toEqual('string') + }) +}) diff --git a/nip28.ts b/nip28.ts new file mode 100644 index 0000000..2d53bc2 --- /dev/null +++ b/nip28.ts @@ -0,0 +1,163 @@ +import {Event, finishEvent, Kind} from './event.ts' + +export interface ChannelMetadata { + name: string + about: string + picture: string +} + +export interface ChannelCreateEventTemplate { + /* JSON string containing ChannelMetadata as defined for Kind 40 and 41 in nip-28. */ + content: string | ChannelMetadata + created_at: number + tags?: string[][] +} + +export interface ChannelMetadataEventTemplate { + channel_create_event_id: string + /* JSON string containing ChannelMetadata as defined for Kind 40 and 41 in nip-28. */ + content: string | ChannelMetadata + created_at: number + tags?: string[][] +} + +export interface ChannelMessageEventTemplate { + channel_create_event_id: string + reply_to_channel_message_event_id?: string + relay_url: string + content: string + created_at: number + tags?: string[][] +} + +export interface ChannelHideMessageEventTemplate { + channel_message_event_id: string + content: string | {reason: string} + created_at: number + tags?: string[][] +} + +export interface ChannelMuteUserEventTemplate { + content: string | {reason: string} + created_at: number + pubkey_to_mute: string + tags?: string[][] +} + +export const channelCreateEvent = ( + t: ChannelCreateEventTemplate, + privateKey: string +): Event | undefined => { + let content: string + if (typeof t.content === 'object') { + content = JSON.stringify(t.content) + } else if (typeof t.content === 'string') { + content = t.content + } else { + return undefined + } + + return finishEvent( + { + kind: Kind.ChannelCreation, + tags: [...(t.tags ?? [])], + content: content, + created_at: t.created_at + }, + privateKey + ) +} + +export const channelMetadataEvent = ( + t: ChannelMetadataEventTemplate, + privateKey: string +): Event | undefined => { + let content: string + if (typeof t.content === 'object') { + content = JSON.stringify(t.content) + } else if (typeof t.content === 'string') { + content = t.content + } else { + return undefined + } + + return finishEvent( + { + kind: Kind.ChannelMetadata, + tags: [['e', t.channel_create_event_id], ...(t.tags ?? [])], + content: content, + created_at: t.created_at + }, + privateKey + ) +} + +export const channelMessageEvent = ( + t: ChannelMessageEventTemplate, + privateKey: string +): Event => { + const tags = [['e', t.channel_create_event_id, t.relay_url, 'root']] + + if (t.reply_to_channel_message_event_id) { + tags.push(['e', t.reply_to_channel_message_event_id, t.relay_url, 'reply']) + } + + return finishEvent( + { + kind: Kind.ChannelMessage, + tags: [...tags, ...(t.tags ?? [])], + content: t.content, + created_at: t.created_at + }, + privateKey + ) +} + +/* "e" tag should be the kind 42 event to hide */ +export const channelHideMessageEvent = ( + t: ChannelHideMessageEventTemplate, + privateKey: string +): Event | undefined => { + let content: string + if (typeof t.content === 'object') { + content = JSON.stringify(t.content) + } else if (typeof t.content === 'string') { + content = t.content + } else { + return undefined + } + + return finishEvent( + { + kind: Kind.ChannelHideMessage, + tags: [['e', t.channel_message_event_id], ...(t.tags ?? [])], + content: content, + created_at: t.created_at + }, + privateKey + ) +} + +export const channelMuteUserEvent = ( + t: ChannelMuteUserEventTemplate, + privateKey: string +): Event | undefined => { + let content: string + if (typeof t.content === 'object') { + content = JSON.stringify(t.content) + } else if (typeof t.content === 'string') { + content = t.content + } else { + return undefined + } + + return finishEvent( + { + kind: Kind.ChannelMuteUser, + tags: [['p', t.pubkey_to_mute], ...(t.tags ?? [])], + content: content, + created_at: t.created_at + }, + privateKey + ) +}