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

@bernierllc/social-media-content-type-mastodon

v1.0.2

Published

Mastodon-specific content type definition and validation

Readme

@bernierllc/social-media-content-type-mastodon

Mastodon-specific content type definition and validation for the BernierLLC Content Management Suite.

Features

  • Instance-specific configuration fetching and caching
  • Content warning (CW) auto-detection and generation
  • Cross-instance mention parsing (@[email protected])
  • Media focal point support
  • Character counting with URL normalization (23 chars per URL)
  • Thread generation for long content
  • Four visibility levels (public, unlisted, private, direct)
  • Poll support (2-4 options, 5min-30days expiry)
  • Custom emoji support

Installation

npm install @bernierllc/social-media-content-type-mastodon

Usage

The package provides three main classes for working with Mastodon content:

  1. MastodonContentBuilder - Fluent API for building Mastodon posts
  2. MastodonContentValidator - Validate content against Mastodon's rules
  3. MastodonContentTransformer - Convert content from other formats

Quick Start

import {
  MastodonContentBuilder,
  MastodonContentValidator,
  MastodonInstanceConfigFetcher
} from '@bernierllc/social-media-content-type-mastodon';

// Fetch instance configuration
const fetcher = new MastodonInstanceConfigFetcher();
const config = await fetcher.fetchConfig('mastodon.social');

// Build content
const builder = new MastodonContentBuilder();
const content = builder
  .setStatus('Hello from @bernierllc! #Mastodon')
  .setVisibility('public')
  .setSensitive(false)
  .build();

// Validate
const validator = new MastodonContentValidator();
const result = validator.validate(content, config);

console.log(`Valid: ${result.valid}`);
console.log(`Character count: ${result.characterCount.status}`);

Core API

MastodonContentBuilder

Fluent builder for creating Mastodon content:

const content = new MastodonContentBuilder()
  .setStatus('My post text')
  .addMedia({ type: 'image', file: '/path/to/image.jpg', description: 'Alt text' })
  .addHashtag('mastodon')
  .addMention('@friend')
  .setVisibility('public')
  .setSpoilerText('NSFW')  // Content warning
  .build();

MastodonContentValidator

Validates content against platform constraints:

const validator = new MastodonContentValidator();
const result = validator.validate(content, instanceConfig);

if (!result.valid) {
  console.error('Validation errors:', result.errors);
}

MastodonInstanceConfigFetcher

Fetches instance-specific limits from Mastodon API:

const fetcher = new MastodonInstanceConfigFetcher();

// Fetches from /api/v1/instance
const config = await fetcher.fetchConfig('mastodon.social');
console.log(`Max status length: ${config.maxStatusLength}`);  // Often 500, can be 5000+

// Cached for 1 hour
const cached = fetcher.getCachedConfig('mastodon.social');

Character Counting

Mastodon counts URLs as 23 characters regardless of actual length:

import { countCharacters, isWithinCharacterLimit } from '@bernierllc/social-media-content-type-mastodon';

const status = 'Check out https://example.com/very/long/url';
const count = countCharacters(status);

console.log(`Total: ${count.total}`); // "Check out " (10) + 23 (URL) = 33
console.log(`URLs: ${count.urlCount}`); // 1

const withinLimit = isWithinCharacterLimit(status, 500); // true

Mention Parsing

Supports local and cross-instance mentions:

import { parseMention, extractMentionsFromStatus } from '@bernierllc/social-media-content-type-mastodon';

// Local mention
const local = parseMention('@alice');
console.log(local.isLocal); // true
console.log(local.formatted); // "@alice"

// Cross-instance mention
const remote = parseMention('@[email protected]');
console.log(remote.isLocal); // false
console.log(remote.instance); // "mastodon.social"

// Extract from status
const status = '@alice and @[email protected] are friends';
const mentions = extractMentionsFromStatus(status);
console.log(mentions.length); // 2

Content Warning Generation

Auto-detect sensitive content and suggest warnings:

import { detectContentWarning, autoGenerateContentWarning } from '@bernierllc/social-media-content-type-mastodon';

const status = 'Discussion about violence and politics';
const detection = detectContentWarning(status);

console.log(detection.suggested); // true
console.log(detection.suggestedText); // "Violence, Politics"
console.log(detection.confidence); // "medium" or "high"

// Auto-generate (only for medium/high confidence, skips if explicit CW exists)
const cw = autoGenerateContentWarning(status);
console.log(cw); // "Violence, Politics"

Thread Generation

Split long content into multiple posts:

import { generateThread } from '@bernierllc/social-media-content-type-mastodon';

const longContent = 'A'.repeat(800); // Exceeds 500 char limit
const thread = generateThread(longContent, instanceConfig, {
  numberPosts: true,  // Add "1/3", "2/3", etc.
  preserveHashtags: 'both',  // Keep hashtags in first and last post
  splitStrategy: 'sentence'  // Split at sentence boundaries
});

console.log(`Posts: ${thread.posts.length}`);
console.log(thread.posts[0]); // "1/3 First part..."

Content Transformation

Transform between content types:

import { MastodonContentTransformer } from '@bernierllc/social-media-content-type-mastodon';

const transformer = new MastodonContentTransformer();

// From Twitter
const twitterContent = {
  text: 'Hello Twitter!',
  media: [{ type: 'photo', file: 'image.jpg', description: 'Alt text' }]
};
const mastodonContent = transformer.fromTwitter(twitterContent);

// From blog post (creates thread if needed)
const blogContent = {
  title: 'My Blog Post',
  excerpt: 'This is the excerpt...',
  url: 'https://myblog.com/post',
  featuredImage: 'cover.jpg',
  tags: ['tech', 'mastodon']
};
const thread = transformer.fromBlogPost(blogContent, instanceConfig);

Platform Constraints

Default constraints (instance-specific, fetchable from API):

  • Max status length: 500 chars (default), up to 10,000+ on some instances
  • Max media: 4 attachments
  • Max poll options: 4 (2 minimum)
  • Poll expiry: 5 minutes to 30 days
  • Max media description: 1,500 chars
  • Focal point range: -1.0 to 1.0 (x/y coordinates)
import { MASTODON_CONSTRAINTS } from '@bernierllc/social-media-content-type-mastodon';

console.log(MASTODON_CONSTRAINTS.maxStatusLength); // 500
console.log(MASTODON_CONSTRAINTS.maxMediaAttachments); // 4

Visibility Levels

  • public: Visible to all, appears in public timelines, federates
  • unlisted: Visible to all, but not in public timelines, federates
  • private: Followers only, federates to followers' instances
  • direct: Mentioned users only, doesn't federate to public

Integration Status

Logger Integration

Status: Not applicable

Justification: This is a pure content type definition package with no runtime operations, side effects, or error conditions that require logging. The MastodonContentValidator, MastodonContentBuilder, and MastodonContentTransformer classes are stateless utility classes that perform validation, building, and transformation operations. All errors are thrown as exceptions that calling code can handle, and there are no background operations, network calls, or state changes that would benefit from structured logging.

Pattern: Pure functional utility - no logger integration needed. Logging is handled by consuming packages that use this content type.

NeverHub Integration

Status: Optional

Justification: This package can optionally register itself with NeverHub for service discovery. Content type packages can register themselves with NeverHub so that services can discover available content types at runtime. This enables dynamic content type discovery and allows services to adapt to available content types without hard-coded dependencies.

Pattern: Optional service discovery integration - package can register content type with NeverHub for runtime discovery.

Example Integration:

import { registerMastodonContentType } from '@bernierllc/social-media-content-type-mastodon/neverhub-registration';

// Register with NeverHub (if available)
if (typeof detectNeverHub === 'function') {
  registerMastodonContentType();
}

Docs-Suite Integration

Status: Ready

Format: TypeDoc-compatible JSDoc comments are included throughout the source code. All public APIs are documented with examples and type information.

License

Copyright (c) 2025 Bernier LLC. All rights reserved.

See Also