@toolkit-p2p/social
v0.1.0
Published
Social graph and Web of Trust implementation for toolkit-p2p
Maintainers
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/socialQuick 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:
- Blocklist (from
@toolkit-p2p/identity) - Content Flags
- 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.NSFWis filtered - Hides 18+ content:
ContentFlag.Adult18Plusis filtered - Hides blocked peers: Content from blocked peers is always hidden
- Shows sensitive content:
ContentFlag.Sensitiveis not filtered by default - Shows spoilers:
ContentFlag.Spoileris 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:
- Blocklist Check (highest priority) - Content from blocked users is ALWAYS hidden
- Reveal Check - If user revealed this content, show it (unless blocked)
- 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:coverageExamples
See the tests/ directory for comprehensive examples:
tests/social-graph.test.ts- Social graph operationstests/flags.test.ts- Content flagging systemtests/content-filter.test.ts- Content filtering logictests/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:
- All tests pass (
pnpm test) - Code follows TypeScript best practices
- New features include tests
- Documentation is updated
Support
For issues, questions, or contributions, visit the toolkit-p2p repository.
