-
Notifications
You must be signed in to change notification settings - Fork 234
feat: add nip-25 support #589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
45bc4dd
feat: add nip-25 support
CKodidela 12002d2
feat: add NIP-25 reactions support with schema validation for kind 7 …
CKodidela 45341de
chore: integrate NIP-65 relay list metadata from main
CKodidela 8dc8f2a
fix: wire nip25 type guards into event schema validation
CKodidela 8b2f9a2
perf: loop once through tags in NIP-25 validation and parsing
CKodidela 880beaa
chore: merge upstream main and resolve NIP-25/NIP-12 conflicts
CKodidela a73ffeb
Merge branch 'main' into feat/nip-25
CKodidela f476e60
Merge branch 'main' into feat/nip-25
cameri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { Event, ReactionEntry } from '../@types/event' | ||
| import { EventKinds, EventTags } from '../constants/base' | ||
|
|
||
| export const isReactionEvent = (event: Event): boolean => event.kind === EventKinds.REACTION | ||
|
|
||
| export const isExternalContentReactionEvent = (event: Event): boolean => | ||
| event.kind === EventKinds.EXTERNAL_CONTENT_REACTION | ||
|
|
||
| export const isLikeReaction = (event: Event): boolean => | ||
| isReactionEvent(event) && (event.content === '+' || event.content === '') | ||
|
|
||
| export const isDislikeReaction = (event: Event): boolean => | ||
| isReactionEvent(event) && event.content === '-' | ||
|
|
||
| export const parseReaction = (event: Event): ReactionEntry => { | ||
| const eTags = event.tags.filter((tag) => tag[0] === EventTags.Event) | ||
|
cameri marked this conversation as resolved.
Outdated
|
||
| const pTags = event.tags.filter((tag) => tag[0] === EventTags.Pubkey) | ||
| const aTags = event.tags.filter((tag) => tag[0] === EventTags.Address) | ||
| const kTag = event.tags.find((tag) => tag[0] === EventTags.Kind) | ||
|
|
||
| return { | ||
| targetEventId: eTags.length > 0 ? eTags[eTags.length - 1][1] : undefined, | ||
| targetPubkey: pTags.length > 0 ? pTags[pTags.length - 1][1] : undefined, | ||
| targetAddress: aTags.length > 0 ? aTags[aTags.length - 1][1] : undefined, | ||
| targetKind: kTag ? Number(kTag[1]) : undefined, | ||
|
CKodidela marked this conversation as resolved.
Outdated
|
||
| content: event.content, | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| Feature: NIP-25 Reactions | ||
| Scenario: Alice likes Bob's note | ||
| Given someone called Alice | ||
| And someone called Bob | ||
| When Bob sends a text_note event with content "hello world" | ||
| And Alice reacts to Bob's note with "+" | ||
| And Alice subscribes to her reaction events | ||
| Then Alice receives a reaction event with content "+" | ||
|
|
||
| Scenario: Alice dislikes Bob's note | ||
| Given someone called Alice | ||
| And someone called Bob | ||
| When Bob sends a text_note event with content "hello world" | ||
| And Alice reacts to Bob's note with "-" | ||
| And Alice subscribes to her reaction events | ||
| Then Alice receives a reaction event with content "-" | ||
|
|
||
| Scenario: Alice reacts with an emoji | ||
| Given someone called Alice | ||
| And someone called Bob | ||
| When Bob sends a text_note event with content "hello world" | ||
| And Alice reacts to Bob's note with "🤙" | ||
| And Alice subscribes to her reaction events | ||
| Then Alice receives a reaction event with content "🤙" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { Then, When, World } from '@cucumber/cucumber' | ||
| import { expect } from 'chai' | ||
| import WebSocket from 'ws' | ||
| import { Event } from '../../../../src/@types/event' | ||
| import { EventKinds } from '../../../../src/constants/base' | ||
| import { createEvent, createSubscription, sendEvent, waitForNextEvent } from '../helpers' | ||
|
|
||
| When(/^(\w+) reacts to (\w+)'s note with "([^"]+)"$/, async function (reactor: string, author: string, content: string) { | ||
| const ws = this.parameters.clients[reactor] as WebSocket | ||
| const { pubkey, privkey } = this.parameters.identities[reactor] | ||
| const targetEvent = this.parameters.events[author][this.parameters.events[author].length - 1] as Event | ||
|
|
||
| const event: Event = await createEvent( | ||
| { | ||
| pubkey, | ||
| kind: EventKinds.REACTION, | ||
| content, | ||
| tags: [ | ||
| ['e', targetEvent.id], | ||
| ['p', targetEvent.pubkey], | ||
| ], | ||
| }, | ||
| privkey, | ||
| ) | ||
|
|
||
| await sendEvent(ws, event) | ||
| this.parameters.events[reactor].push(event) | ||
| }) | ||
|
|
||
| When(/^(\w+) subscribes to (?:her|his|their) reaction events$/, async function (this: World<Record<string, any>>, name: string) { | ||
| const ws = this.parameters.clients[name] as WebSocket | ||
| const { pubkey } = this.parameters.identities[name] | ||
| const subscription = { | ||
| name: `test-${Math.random()}`, | ||
| filters: [{ kinds: [EventKinds.REACTION], authors: [pubkey] }], | ||
| } | ||
| this.parameters.subscriptions[name].push(subscription) | ||
|
|
||
| await createSubscription(ws, subscription.name, subscription.filters) | ||
| }) | ||
|
|
||
| Then(/^(\w+) receives a reaction event with content "([^"]+)"$/, async function (name: string, content: string) { | ||
| const ws = this.parameters.clients[name] as WebSocket | ||
| const subscription = this.parameters.subscriptions[name][this.parameters.subscriptions[name].length - 1] | ||
| const receivedEvent = await waitForNextEvent(ws, subscription.name) | ||
|
|
||
| expect(receivedEvent.kind).to.equal(EventKinds.REACTION) | ||
| expect(receivedEvent.content).to.equal(content) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import { expect } from 'chai' | ||
| import { Event } from '../../../src/@types/event' | ||
| import { EventKinds } from '../../../src/constants/base' | ||
| import { | ||
| isDislikeReaction, | ||
| isExternalContentReactionEvent, | ||
| isLikeReaction, | ||
| isReactionEvent, | ||
| parseReaction, | ||
| } from '../../../src/utils/nip25' | ||
|
|
||
| const baseEvent = (): Partial<Event> => ({ tags: [], content: '+' }) | ||
|
|
||
| describe('NIP-25', () => { | ||
| describe('isReactionEvent', () => { | ||
| it('returns true for kind 7', () => | ||
| expect(isReactionEvent({ ...baseEvent(), kind: EventKinds.REACTION } as Event)).to.equal(true)) | ||
|
|
||
| it('returns false for other kinds', () => | ||
| expect(isReactionEvent({ ...baseEvent(), kind: EventKinds.TEXT_NOTE } as Event)).to.equal(false)) | ||
| }) | ||
|
|
||
| describe('isExternalContentReactionEvent', () => { | ||
| it('returns true for kind 17', () => | ||
| expect( | ||
| isExternalContentReactionEvent({ ...baseEvent(), kind: EventKinds.EXTERNAL_CONTENT_REACTION } as Event), | ||
| ).to.equal(true)) | ||
|
|
||
| it('returns false for kind 7', () => | ||
| expect( | ||
| isExternalContentReactionEvent({ ...baseEvent(), kind: EventKinds.REACTION } as Event), | ||
| ).to.equal(false)) | ||
| }) | ||
|
|
||
| describe('isLikeReaction', () => { | ||
| it('returns true for "+"', () => | ||
| expect(isLikeReaction({ ...baseEvent(), kind: EventKinds.REACTION, content: '+' } as Event)).to.equal(true)) | ||
|
|
||
| it('returns true for empty content', () => | ||
| expect(isLikeReaction({ ...baseEvent(), kind: EventKinds.REACTION, content: '' } as Event)).to.equal(true)) | ||
|
|
||
| it('returns false for "-"', () => | ||
| expect(isLikeReaction({ ...baseEvent(), kind: EventKinds.REACTION, content: '-' } as Event)).to.equal(false)) | ||
| }) | ||
|
|
||
| describe('isDislikeReaction', () => { | ||
| it('returns true for "-"', () => | ||
| expect(isDislikeReaction({ ...baseEvent(), kind: EventKinds.REACTION, content: '-' } as Event)).to.equal(true)) | ||
|
|
||
| it('returns false for "+"', () => | ||
| expect(isDislikeReaction({ ...baseEvent(), kind: EventKinds.REACTION, content: '+' } as Event)).to.equal(false)) | ||
| }) | ||
|
|
||
| describe('parseReaction', () => { | ||
| it('picks the last e tag as targetEventId', () => { | ||
| const event = { | ||
| ...baseEvent(), | ||
| kind: EventKinds.REACTION, | ||
| tags: [['e', 'aaa'], ['e', 'bbb']], | ||
| } as unknown as Event | ||
| expect(parseReaction(event).targetEventId).to.equal('bbb') | ||
| }) | ||
|
|
||
| it('picks the last p tag as targetPubkey', () => { | ||
| const event = { | ||
| ...baseEvent(), | ||
| kind: EventKinds.REACTION, | ||
| tags: [['p', 'pk1'], ['p', 'pk2']], | ||
| } as unknown as Event | ||
| expect(parseReaction(event).targetPubkey).to.equal('pk2') | ||
| }) | ||
|
|
||
| it('parses k tag as targetKind number', () => { | ||
| const event = { | ||
| ...baseEvent(), | ||
| kind: EventKinds.REACTION, | ||
| tags: [['k', '1']], | ||
| } as unknown as Event | ||
| expect(parseReaction(event).targetKind).to.equal(1) | ||
| }) | ||
|
|
||
| it('returns undefined fields when tags are absent', () => { | ||
| const event = { ...baseEvent(), kind: EventKinds.REACTION, tags: [] } as unknown as Event | ||
| const result = parseReaction(event) | ||
| expect(result.targetEventId).to.be.undefined | ||
| expect(result.targetPubkey).to.be.undefined | ||
| expect(result.targetKind).to.be.undefined | ||
| }) | ||
| }) | ||
| }) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.