npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@toolkit-p2p/social

v0.1.0

Published

Social graph and Web of Trust implementation for toolkit-p2p

Readme

@toolkit-p2p/social

Social graph and Web of Trust implementation for toolkit-p2p. Provides follow/unfollow operations, trust score propagation, content filtering, and moderation features.

Features

  • Social Graph Management: Follow/unfollow peers, query follow relationships
  • Web of Trust: Trust score calculation and propagation through the social graph
  • Content Moderation: Blocklist, content flags, and user-customizable filtering
  • Recommendation Engine: Discover new peers based on your social graph
  • Local Storage: Persistent storage using IndexedDB for social graph data

Installation

pnpm add @toolkit-p2p/social

Quick Start

Social Graph

import { SocialGraph } from '@toolkit-p2p/social';

// Initialize social graph
const social = new SocialGraph();
await social.init();

// Follow a peer
await social.follow('did:zeta:alice123');

// Unfollow a peer
await social.unfollow('did:zeta:bob456');

// Check if you're following someone
const following = await social.isFollowing('did:zeta:alice123');

// Get all peers you follow
const following = await social.getFollowing();

// Get peers who follow you (requires querying their graphs)
const followers = await social.getFollowers();

// Get follow recommendations
const recommendations = await social.getRecommendations();

Content Moderation

The social package provides a comprehensive content moderation system with three layers:

  1. Blocklist (from @toolkit-p2p/identity)
  2. Content Flags
  3. Content Filtering

Blocklist

Block unwanted peers to hide all their content:

import { blockDid, unblockDid, isBlocked, listBlocked } from '@toolkit-p2p/identity';

// Block a peer
await blockDid('did:zeta:spammer123');

// Unblock a peer
await unblockDid('did:zeta:spammer123');

// Check if peer is blocked
const blocked = await isBlocked('did:zeta:spammer123');

// List all blocked peers
const blockedList = await listBlocked();

Content Flags

Flag content with standardized labels:

import { ContentFlag } from '@toolkit-p2p/social';

// Available flags
ContentFlag.NSFW          // "nsfw" - Not Safe For Work
ContentFlag.Adult18Plus   // "18+" - Adult content
ContentFlag.Sensitive     // "sensitive" - Potentially upsetting content
ContentFlag.Spoiler       // "spoiler" - Spoilers for media/entertainment

// Example: Flag content
const post = {
  contentId: 'post-123',
  author: 'did:zeta:alice',
  flags: [ContentFlag.NSFW, ContentFlag.Sensitive]
};

Content Filtering

Filter content based on blocklist and content flags with user preferences:

import { ContentFilterService, ContentFlag } from '@toolkit-p2p/social';

// Initialize filter service
const filter = new ContentFilterService();
await filter.loadPreferences();

// Filter a single post
const post = {
  contentId: 'post-123',
  author: 'did:zeta:alice',
  flags: [ContentFlag.NSFW]
};

const result = await filter.filterContent(post);
if (result.shouldHide && !result.wasRevealed) {
  console.log(`Hidden: ${result.reason}`);
  // Show "Content hidden" UI with reveal button
} else {
  // Show content
}

// Filter multiple posts
const posts = await getPostsFromFeed();
const visiblePosts = await filter.filterContentList(posts);

// Customize filter preferences
await filter.setPreferences({
  hideFlags: [ContentFlag.NSFW, ContentFlag.Spoiler],
  hideBlocked: true
});

// Reveal specific hidden content
await filter.revealContent('post-123');

// Hide previously revealed content
await filter.hideContent('post-123');

// Clear all revealed content
await filter.clearRevealedContent();

Default Filter Behavior

By default, the ContentFilterService:

  • Hides NSFW content: ContentFlag.NSFW is filtered
  • Hides 18+ content: ContentFlag.Adult18Plus is filtered
  • Hides blocked peers: Content from blocked peers is always hidden
  • Shows sensitive content: ContentFlag.Sensitive is not filtered by default
  • Shows spoilers: ContentFlag.Spoiler is not filtered by default

Blocklist Priority: Content from blocked peers is ALWAYS hidden, even if the user has revealed it. This ensures that blocking works as a hard filter.

Filter Priority Order

Content is filtered in this order:

  1. Blocklist Check (highest priority) - Content from blocked users is ALWAYS hidden
  2. Reveal Check - If user revealed this content, show it (unless blocked)
  3. Content Flags Check - Hide if flags match user preferences
// Example: Blocklist overrides reveal
await blockDid('did:zeta:mallory');

const content = {
  contentId: 'post-from-mallory',
  author: 'did:zeta:mallory',
  flags: [ContentFlag.NSFW]
};

// Even if user reveals this content...
await filter.revealContent('post-from-mallory');

// ...it will still be hidden because author is blocked
const result = await filter.filterContent(content);
console.log(result.shouldHide); // true
console.log(result.reason); // "Content from blocked peer"

API Reference

SocialGraph

constructor(storage?: SocialGraphStorage)

Create a new social graph instance. Optionally provide custom storage backend.

async init(): Promise<void>

Initialize the social graph. Must be called before any other operations.

async follow(did: string): Promise<void>

Follow a peer by their DID.

async unfollow(did: string): Promise<void>

Unfollow a peer.

async isFollowing(did: string): Promise<boolean>

Check if you're following a specific peer.

async getFollowing(): Promise<string[]>

Get list of all peers you follow.

async getFollowers(): Promise<string[]>

Get list of all peers who follow you (requires their social graph data).

async getRecommendations(limit?: number): Promise<Recommendation[]>

Get peer recommendations based on your social graph.

ContentFilterService

constructor(preferences?: ContentPreferences)

Create a filter service with optional custom preferences.

async loadPreferences(): Promise<void>

Load user preferences from localStorage.

async savePreferences(): Promise<void>

Save current preferences to localStorage.

getPreferences(): ContentPreferences

Get current content preferences.

async setPreferences(preferences: Partial<ContentPreferences>): Promise<void>

Update content preferences (partial update supported).

async filterContent(content: FilterableContent): Promise<FilterResult>

Filter a single piece of content. Returns filtering decision with metadata.

async filterContentList(contentList: FilterableContent[]): Promise<FilterableContent[]>

Filter multiple pieces of content. Returns only visible content.

async revealContent(contentId: string): Promise<void>

Mark hidden content as revealed (user chose to view it).

async hideContent(contentId: string): Promise<void>

Hide previously revealed content.

async isContentHidden(content: FilterableContent): Promise<boolean>

Check if content would be hidden by current filters.

async clearRevealedContent(): Promise<void>

Clear all revealed content preferences.

TypeScript Types

ContentPreferences

interface ContentPreferences {
  /** Hide content with these flags */
  hideFlags?: ContentFlag[];
  /** Hide content from blocked peers */
  hideBlocked?: boolean;
  /** Revealed content IDs (user chose to view despite filters) */
  revealedContent?: Set<string>;
}

FilterResult

interface FilterResult {
  /** Whether content should be hidden */
  shouldHide: boolean;
  /** Reason for hiding (if applicable) */
  reason?: string;
  /** Flags that caused hiding */
  matchedFlags?: ContentFlag[];
  /** Whether content was revealed by user */
  wasRevealed: boolean;
}

FilterableContent

interface FilterableContent {
  /** Content ID */
  contentId: string;
  /** Author peer ID */
  author: string;
  /** Content flags */
  flags?: ContentFlag[];
}

ContentFlag

enum ContentFlag {
  NSFW = 'nsfw',
  Adult18Plus = '18+',
  Sensitive = 'sensitive',
  Spoiler = 'spoiler'
}

Advanced Usage

Custom Filter Preferences

// Hide spoilers but show NSFW (if user is 18+)
await filter.setPreferences({
  hideFlags: [ContentFlag.Spoiler],
  hideBlocked: true
});

// Disable blocklist filtering (show content from all users)
await filter.setPreferences({
  hideBlocked: false
});

// Hide all flagged content
await filter.setPreferences({
  hideFlags: [
    ContentFlag.NSFW,
    ContentFlag.Adult18Plus,
    ContentFlag.Sensitive,
    ContentFlag.Spoiler
  ]
});

Building a Content Feed with Filtering

import { ContentFilterService, ContentFlag } from '@toolkit-p2p/social';
import { blockDid } from '@toolkit-p2p/identity';

// Initialize filter
const filter = new ContentFilterService();
await filter.loadPreferences();

// Block spam accounts
await blockDid('did:zeta:spammer1');
await blockDid('did:zeta:spammer2');

// Fetch posts from network
const rawPosts = await fetchPostsFromNetwork();

// Apply filters
const visiblePosts = await filter.filterContentList(rawPosts);

// Render feed
visiblePosts.forEach(post => {
  renderPost(post);
});

// Handle hidden content (show "Content Hidden" placeholder)
const hiddenPosts = rawPosts.filter(post =>
  !visiblePosts.some(visible => visible.contentId === post.contentId)
);

hiddenPosts.forEach(async post => {
  const result = await filter.filterContent(post);
  renderHiddenContentPlaceholder(post, result.reason, result.matchedFlags);
});

Reveal/Hide Workflow

// User clicks "Show Content" button on hidden NSFW post
async function handleRevealContent(contentId: string) {
  await filter.revealContent(contentId);
  // Re-render feed to show the post
  await refreshFeed();
}

// User clicks "Hide Again" button on revealed post
async function handleHideContent(contentId: string) {
  await filter.hideContent(contentId);
  // Re-render feed to hide the post
  await refreshFeed();
}

// User wants to reset all reveal preferences
async function handleResetReveals() {
  await filter.clearRevealedContent();
  await refreshFeed();
}

Persistence

Social Graph Storage

The social graph is persisted to IndexedDB automatically:

  • Database: toolkit-p2p:social-graph
  • Store: following
  • Each entry: { did: string, timestamp: number }

Content Preferences Storage

Content filter preferences are persisted to localStorage:

  • Key: toolkit-p2p:content-preferences
  • Format: JSON
  • Contains: hideFlags, hideBlocked, revealedContent

Blocklist Storage

The blocklist is persisted via @toolkit-p2p/identity:

  • Database: toolkit-p2p:identity
  • Store: blocklist
  • Managed by identity package

Testing

# Run all tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Run tests with coverage
pnpm test:coverage

Examples

See the tests/ directory for comprehensive examples:

  • tests/social-graph.test.ts - Social graph operations
  • tests/flags.test.ts - Content flagging system
  • tests/content-filter.test.ts - Content filtering logic
  • tests/moderation-integration.test.ts - Complete moderation workflows

Dependencies

  • @toolkit-p2p/graph - Graph data structure for social relationships
  • @toolkit-p2p/identity - Identity and blocklist management

License

MIT

Contributing

Contributions are welcome! Please ensure:

  1. All tests pass (pnpm test)
  2. Code follows TypeScript best practices
  3. New features include tests
  4. Documentation is updated

Support

For issues, questions, or contributions, visit the toolkit-p2p repository.