Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ Chorus implements [NIP-72](https://github.com/nostr-protocol/nips/blob/master/72

- **Kind 34550**: Community definition events that include community metadata and moderator lists
- **Kind 4550**: Post approval events that moderators use to approve posts
- **Kind 11**: Text note events used for posts within communities
- **Kind 1111**: Text note events used for posts within communities

**📋 IMPORTANT: This project extends NIP-72 with custom event kinds documented in `NIP.md`. When working with group functionality, always reference this specification and update it when making changes to event kinds or their usage patterns.**

Expand Down
150 changes: 120 additions & 30 deletions NIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,35 @@

**⚠️ DISCLAIMER: This NIP is still under active development and subject to change. The event kinds and specifications described here are experimental and may be modified, deprecated, or replaced in future versions. It is not recommended to implement this NIP in production systems without first discussing it with the Chorus development team.**

This document describes the Chorus platform's extensions to NIP-72 (Moderated Communities) that enhance community management, user moderation, and content organization capabilities.
This document describes the Chorus platform's extensions to NIP-72 (Moderated Communities) that enhance community management, user moderation, and content organization capabilities. Chorus implements NIP-22 (Comment) for all group discussions, treating the community itself as the root event for threaded conversations.

## Background

NIP-72 defines the basic framework for moderated communities on Nostr using:
- **Kind 34550**: Community definition events
- **Kind 4550**: Post approval events

Chorus extends this foundation with additional event kinds to provide comprehensive community management features including member lists, content pinning, join requests, and enhanced moderation workflows.
NIP-22 defines a comment threading system using:
- **Kind 1111**: Comments scoped to a root event

Chorus combines these specifications, using NIP-22 comments scoped to NIP-72 communities for all group discussions, and extends this foundation with additional event kinds to provide comprehensive community management features including member lists, content pinning, join requests, and enhanced moderation workflows.

## Core NIP-72 Event Kinds

### Kind 34550: Community Definition
Defines a community with metadata and moderator lists as specified in NIP-72.

### Kind 4550: Post Approval
Moderator approval events for posts as specified in NIP-72.
Moderator approval events for comments as specified in NIP-72, extended to handle Kind 1111 comments.

**Tags:**
- `["a", communityId]` - References the target community
- `["e", commentId]` - References the approved comment
- `["p", commentAuthorPubkey]` - References the comment author
- `["k", "1111"]` - Kind of the approved comment

**Content:**
Contains the full JSON of the approved comment event for redistribution.

## Chorus Extensions

Expand Down Expand Up @@ -148,9 +160,9 @@ Moderator approval events for posts as specified in NIP-72.

**Tags:**
- `["a", communityId]` - References the target community
- `["e", eventId]` - References the removed post
- `["p", authorPubkey]` - References the post author
- `["k", originalKind]` - Kind of the original post
- `["e", eventId]` - References the removed comment
- `["p", authorPubkey]` - References the comment author
- `["k", "1111"]` - Kind of the original comment

**Content:**
The content field can be left blank or optionally include a moderation reason.
Expand All @@ -165,7 +177,7 @@ The content field can be left blank or optionally include a moderation reason.
["a", "34550:community_creator_pubkey:bitcoin-discussion"],
["e", "removed_post_id"],
["p", "post_author_pubkey"],
["k", "1"]
["k", "1111"]
],
"content": "Removed for violating community guidelines"
}
Expand Down Expand Up @@ -232,45 +244,63 @@ The content field can be left blank or optionally include a moderation reason.
}
```

### Enhanced Post Events
### Group Discussion Events (NIP-22 Implementation)

#### Kind 11: Group Post
Standard text note posted to a community, extending Kind 1 with community targeting.
All group discussions in Chorus use **Kind 1111** (NIP-22 Comments) scoped to the community as the root event. This provides proper threading while maintaining compatibility with the broader Nostr ecosystem.

**Tags:**
- `["a", communityId]` - References the target community
#### Kind 1111: Group Comment (Top-Level Post)
A top-level post in a community, implemented as a NIP-22 comment scoped to the community.

**Tags (NIP-22 compliant):**
- `["A", communityId]` - Root scope: the community (uppercase)
- `["K", "34550"]` - Root kind: community (uppercase)
- `["P", communityCreatorPubkey]` - Root author: community creator (uppercase)
- `["a", communityId]` - Parent scope: same as root for top-level posts (lowercase)
- `["k", "34550"]` - Parent kind: community (lowercase)
- `["p", communityCreatorPubkey]` - Parent author: community creator (lowercase)

**Example:**
```json
{
"kind": 11,
"kind": 1111,
"pubkey": "user_pubkey",
"created_at": 1234567890,
"tags": [
["a", "34550:community_creator_pubkey:bitcoin-discussion"]
["A", "34550:community_creator_pubkey:bitcoin-discussion"],
["K", "34550"],
["P", "community_creator_pubkey"],
["a", "34550:community_creator_pubkey:bitcoin-discussion"],
["k", "34550"],
["p", "community_creator_pubkey"]
],
"content": "What do you think about the latest Bitcoin price movement?"
}
```

#### Kind 1111: Group Post Reply
Reply to a group post, enabling threaded discussions within communities.
#### Kind 1111: Group Comment (Reply)
A reply to another comment in a community, following NIP-22 threading rules.

**Tags:**
- `["a", communityId]` - References the target community
- `["e", parentPostId]` - References the parent post being replied to
- `["p", parentAuthorPubkey]` - References the author of the parent post
**Tags (NIP-22 compliant):**
- `["A", communityId]` - Root scope: the community (uppercase)
- `["K", "34550"]` - Root kind: community (uppercase)
- `["P", communityCreatorPubkey]` - Root author: community creator (uppercase)
- `["e", parentCommentId]` - Parent event: the comment being replied to (lowercase)
- `["k", "1111"]` - Parent kind: comment (lowercase)
- `["p", parentCommentAuthorPubkey]` - Parent author: comment author (lowercase)

**Example:**
```json
{
"kind": 1111,
"pubkey": "replying_user_pubkey",
"pubkey": "replying_user_pubkey",
"created_at": 1234567890,
"tags": [
["a", "34550:community_creator_pubkey:bitcoin-discussion"],
["e", "parent_post_id"],
["p", "parent_post_author_pubkey"]
["A", "34550:community_creator_pubkey:bitcoin-discussion"],
["K", "34550"],
["P", "community_creator_pubkey"],
["e", "parent_comment_id"],
["k", "1111"],
["p", "parent_comment_author_pubkey"]
],
"content": "I think the price movement is due to institutional adoption increasing."
}
Expand All @@ -297,25 +327,78 @@ Reply to a group post, enabling threaded discussions within communities.

### Auto-Approval Workflow

Posts from users in the approved members list (Kind 34551) are automatically considered approved without requiring individual Kind 4550 approval events. This reduces moderation overhead for trusted community members.
Comments from users in the approved members list (Kind 34551) are automatically considered approved without requiring individual Kind 4550 approval events. This reduces moderation overhead for trusted community members.

### NIP-22 Threading Implementation

Chorus implements NIP-22 threading with the community (Kind 34550) as the root event:

1. **Top-level comments**: Both uppercase (root) and lowercase (parent) tags point to the community
2. **Nested replies**: Uppercase tags point to the community (root), lowercase tags point to the parent comment
3. **Querying top-level posts**: Filter Kind 1111 events where parent kind (`k` tag) is "34550"
4. **Querying replies**: Filter Kind 1111 events where parent kind (`k` tag) is "1111"

This approach ensures proper threading while maintaining the community as the central organizing principle.

### Query Patterns

**Top-level posts in a community:**
```json
{
"kinds": [1111],
"#A": ["34550:creator_pubkey:community_identifier"],
"limit": 50
}
```
Then filter results where the `k` tag value is "34550".

**Replies to a specific comment:**
```json
{
"kinds": [1111],
"#e": ["parent_comment_id"],
"limit": 100
}
```

**All comments in a community (posts + replies):**
```json
{
"kinds": [1111],
"#A": ["34550:creator_pubkey:community_identifier"],
"limit": 100
}
```

**Approvals for comments in a community:**
```json
{
"kinds": [4550],
"#a": ["34550:creator_pubkey:community_identifier"],
"#k": ["1111"],
"limit": 50
}
```

### Moderation Hierarchy

1. **Community Creator**: Has full control over the community
2. **Moderators**: Listed in the community definition with `["p", pubkey, relay, "moderator"]` tags
3. **Approved Members**: Can post without individual approval
4. **Regular Members**: Posts require moderator approval
3. **Approved Members**: Can post comments without individual approval
4. **Regular Members**: Comments require moderator approval
5. **Banned Users**: Cannot post, all content hidden

### Client Implementation

Clients SHOULD:
- Display approved posts by default
- Display approved comments by default
- Provide toggles to view pending/unapproved content for moderators
- Hide content from banned users
- Show visual indicators for pinned posts
- Implement join request workflows for private communities
- Support threaded replies within communities
- Support NIP-22 threaded replies within communities
- Properly distinguish between top-level comments (parent kind "34550") and nested replies (parent kind "1111")
- Query using appropriate tag filters (`#A` for root scope, `#e` for parent events)

## Security Considerations

Expand All @@ -326,4 +409,11 @@ Clients SHOULD:

## Compatibility

These extensions are designed to be backward compatible with NIP-72. Clients that only implement basic NIP-72 will still function but may not display the enhanced moderation and organization features.
These extensions are designed to be compatible with both NIP-72 and NIP-22. Clients that implement:

- **Basic NIP-72 only**: Will see community definitions and approvals but not threaded discussions
- **NIP-22 only**: Will see threaded comments but may not understand community context
- **Both NIP-72 and NIP-22**: Will have full functionality including threaded community discussions
- **Chorus extensions**: Will have access to enhanced moderation and organization features

The use of NIP-22 for group discussions ensures broader interoperability with other Nostr clients that support comment threading.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
- Kind 1984: Report

### Groups
- Kind 11: Post in a group
- Kind 1111: Reply to group posts
- Kind 11: Legacy group posts (backwards compatibility)
- Kind 1111: Group comments (NIP-22) - used for all new group discussions
- Kind 4550: Post approval
- Kind 4551: Post removal
- Kind 4552: Join request
Expand Down
2 changes: 1 addition & 1 deletion src/components/EmojiReactionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function EmojiReactionButton({ postId }: EmojiReactionButtonProps) {
kind: KINDS.REACTION,
tags: [
["e", postId],
["k", String(KINDS.GROUP_POST)], // Assuming we're reacting to a kind 11 post
["k", String(KINDS.GROUP_COMMENT)], // Reacting to a kind 1111 comment
],
content: emojiData.emoji, // The emoji character
});
Expand Down
14 changes: 12 additions & 2 deletions src/components/groups/CreatePostForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,25 @@ ${mediaUrl}`;
? hashtagMatches.map(hashtag => ["t", hashtag.slice(1).toLowerCase()])
: [];

// NIP-22 compliant tags for top-level group comment
const tags = [
// Root scope: the group itself (uppercase tags)
["A", communityId],
["K", "34550"], // Group kind
["P", parsedId.pubkey], // Group author

// Parent scope: same as root for top-level posts (lowercase tags)
["a", communityId],
["subject", `Post in ${parsedId?.identifier || 'group'}`],
["k", "34550"], // Group kind
["p", parsedId.pubkey], // Group author

// Additional tags
...imageTags,
...hashtagTags,
];

await publishEvent({
kind: KINDS.GROUP_POST,
kind: KINDS.GROUP_COMMENT,
tags,
content: finalContent,
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/groups/GroupPostItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function ReplyCount({ postId }: { postId: string }) {

try {
const events = await nostr.query([{
kinds: [KINDS.GROUP_POST_REPLY],
kinds: [KINDS.GROUP_COMMENT],
"#e": [postId],
limit: 100,
}], { signal: AbortSignal.timeout(3000) });
Expand Down
2 changes: 1 addition & 1 deletion src/components/groups/PendingRepliesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function PendingReplyItem({ reply, communityId, onApproved }: PendingReplyItemPr
["a", communityId],
["e", reply.id],
["p", reply.pubkey],
["k", "1111"], // Reply kind
["k", String(reply.kind)],
],
content: JSON.stringify(reply),
});
Expand Down
Loading