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-twitter

v1.0.4

Published

Twitter/X-specific content type definition and validation

Readme

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

Twitter/X-specific content type definition and validation package for BernierLLC social media management system.

Installation

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

Features

  • Platform-specific Constraints - Twitter/X character limits, media rules, poll configuration
  • Accurate Character Counting - Handles Twitter's URL shortening (23 chars), emojis, and media
  • Thread Generation - Automatically split long content into threaded tweets
  • Content Validation - Comprehensive validation against Twitter's platform rules
  • Content Transformation - Convert between generic social content and Twitter format
  • Fluent Builder API - Easy content creation with method chaining
  • TypeScript First - Full type safety with strict mode

Usage

Basic Tweet Creation

import { TwitterContentBuilder } from '@bernierllc/social-media-content-type-twitter';

const builder = new TwitterContentBuilder();

const tweet = builder
  .setText('Hello, Twitter! Check out our new product.')
  .addImage('https://example.com/product.jpg', 'Product screenshot')
  .addHashtag('product')
  .addHashtag('launch')
  .build();

console.log(tweet);
// {
//   text: 'Hello, Twitter! Check out our new product.',
//   media: [{ type: 'image', url: '...', altText: '...' }]
// }

Content Validation

import { TwitterContentValidator } from '@bernierllc/social-media-content-type-twitter';

const validator = new TwitterContentValidator();

const result = validator.validate({
  text: 'My tweet text with https://example.com',
  media: [
    { type: 'image', url: 'https://example.com/image.jpg' }
  ]
});

if (result.valid) {
  console.log('Tweet is valid!');
  console.log('Character count:', result.textLength);
} else {
  console.error('Validation errors:', result.errors);
}

Character Counting

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

// Simple character count (URLs = 23 chars)
const count = countCharacters('Check https://example.com for info');
console.log(count); // "Check " (6) + 23 + " for info" (9) = 38

// Detailed breakdown
const breakdown = getCharacterBreakdown(
  'Visit https://example.com and https://test.com',
  true // has media
);

console.log(breakdown);
// {
//   total: 61,
//   text: 15,
//   urls: 46,  // 2 URLs × 23 chars
//   urlCount: 2,
//   media: 23,
//   remaining: 219
// }

Thread Generation

import { splitIntoThread } from '@bernierllc/social-media-content-type-twitter';

const longText = `
This is a very long blog post that exceeds Twitter's 280 character limit.
It will be automatically split into multiple tweets while preserving
sentence boundaries and hashtags. The thread generator is smart enough
to handle URLs, emojis, and special characters correctly.
`;

const thread = splitIntoThread(longText, {
  numberTweets: true,           // Add "1/5", "2/5", etc.
  preserveHashtags: 'both',     // Add hashtags to first and last
  maxLength: 280
});

console.log(thread);
// [
//   { text: 'This is a very long blog post... 1/3' },
//   { text: 'It will be automatically split... 2/3' },
//   { text: 'to handle URLs, emojis... 3/3' }
// ]

Content Transformation

import { TwitterContentTransformer } from '@bernierllc/social-media-content-type-twitter';

const transformer = new TwitterContentTransformer();

// From generic social content
const generic = {
  text: 'Check out my blog post',
  links: ['https://blog.example.com/post'],
  hashtags: ['blog', 'tech'],
  images: ['https://blog.example.com/image.jpg']
};

const twitter = transformer.fromGeneric(generic);
// {
//   text: 'Check out my blog post https://blog.example.com/post #blog #tech',
//   media: [{ type: 'image', url: 'https://blog.example.com/image.jpg' }]
// }

// From blog post to thread
const blog = {
  title: 'My Awesome Blog Post',
  excerpt: 'A detailed look at...',
  url: 'https://blog.example.com/post',
  featuredImage: 'https://blog.example.com/image.jpg',
  tags: ['tech', 'tutorial']
};

const blogThread = transformer.fromBlogPost(blog);
// [
//   {
//     text: 'My Awesome Blog Post https://blog.example.com/post #tech #tutorial',
//     media: [{ type: 'image', url: '...' }]
//   },
//   { text: 'A detailed look at...' }
// ]

Poll Creation

const builder = new TwitterContentBuilder();

const pollTweet = builder
  .setText('What is your favorite programming language?')
  .createPoll(
    ['JavaScript', 'TypeScript', 'Python', 'Go'],
    1440  // Duration in minutes (24 hours)
  )
  .build();

console.log(pollTweet);
// {
//   text: 'What is your favorite programming language?',
//   poll: {
//     options: ['JavaScript', 'TypeScript', 'Python', 'Go'],
//     durationMinutes: 1440
//   }
// }

Reply and Quote Tweets

// Reply to a tweet
const reply = builder
  .setText('Great point! I completely agree.')
  .setReplyTo('1234567890')  // Tweet ID to reply to
  .build();

// Quote tweet
const quote = builder
  .setText('This is exactly what I was thinking!')
  .setQuoteTweet('9876543210')  // Tweet ID to quote
  .build();

API Reference

Types

TwitterContent

interface TwitterContent {
  text: string;
  media?: TwitterMedia[];
  quoteTweet?: string;
  replyTo?: string;
  poll?: TwitterPoll;
}

TwitterMedia

interface TwitterMedia {
  type: 'image' | 'video' | 'gif';
  url: string;
  altText?: string;
  dimensions?: {
    width: number;
    height: number;
  };
}

TwitterPoll

interface TwitterPoll {
  options: string[];        // 2-4 options
  durationMinutes: number;  // 5-10080 (7 days)
}

Classes

TwitterContentBuilder

Fluent builder for creating Twitter content.

Methods:

  • setText(text: string): this - Set tweet text
  • addImage(url: string, altText?: string): this - Add image
  • addVideo(url: string, altText?: string): this - Add video
  • addGif(url: string, altText?: string): this - Add GIF
  • createPoll(options: string[], durationMinutes: number): this - Add poll
  • setReplyTo(tweetId: string): this - Set reply-to tweet
  • setQuoteTweet(tweetId: string): this - Set quote tweet
  • addHashtag(tag: string): this - Add hashtag to metadata
  • addMention(username: string): this - Add mention to metadata
  • build(): TwitterContent - Build content
  • buildWithMetadata() - Build content with metadata
  • validate(): TwitterValidationResult - Validate current content
  • reset(): this - Reset builder
  • clone(): TwitterContentBuilder - Clone builder state

TwitterContentValidator

Validates Twitter content against platform constraints.

Methods:

  • validate(content: TwitterContent): TwitterValidationResult - Full validation
  • validateText(text: string, hasMedia?: boolean): TwitterValidationError[]
  • validateMedia(media: TwitterMedia[]): TwitterValidationError[]
  • validatePoll(poll: TwitterPoll): TwitterValidationError[]
  • countCharacters(text: string): number

TwitterContentTransformer

Transforms content between formats.

Methods:

  • fromGeneric(content: GenericSocialContent): TwitterContent
  • toGeneric(content: TwitterContent): GenericSocialContent
  • fromBlogPost(content: BlogPostContent): TwitterContent[]
  • optimizeForTwitter(content: TwitterContent): TwitterContent
  • splitIntoThread(longText: string): TwitterContent[]

Functions

Character Counting

  • countCharacters(text: string): number - Count characters with Twitter rules
  • countCharactersWithMedia(text: string, hasMedia: boolean): number
  • extractUrls(text: string): string[] - Extract URLs from text
  • getCharacterBreakdown(text, hasMedia, maxLength): CharacterBreakdown
  • exceedsLimit(text, hasMedia, maxLength): boolean

Thread Generation

  • splitIntoThread(longText: string, options?: ThreadOptions): TwitterContent[]
  • estimateTweetCount(text: string, options?: ThreadOptions): number
  • needsThread(text: string, maxLength?: number): boolean

Constants

TWITTER_CONSTRAINTS

{
  maxTextLength: 280,
  maxTextLengthLongForm: 25000,
  maxMediaItems: 4,
  maxPollOptions: 4,
  minPollOptions: 2,
  maxPollDuration: 10080,      // 7 days
  minPollDuration: 5,
  maxAltTextLength: 1000,
  maxHashtags: 30,
  urlLength: 23,                // t.co shortening
  mediaLength: 23,
  supportedMediaTypes: ['image/png', 'image/jpeg', 'image/gif', 'video/mp4'],
  maxImageSize: 5242880,        // 5MB
  maxVideoSize: 536870912,      // 512MB
  imageDimensions: {
    minWidth: 4,
    minHeight: 4,
    maxWidth: 8192,
    maxHeight: 8192
  }
}

Twitter Character Counting Rules

Twitter has specific rules for character counting:

  1. URLs - Always count as 23 characters (due to t.co shortening)
  2. Media - Counts as 23 characters when attached
  3. Emojis - Count based on their Unicode encoding (1-4 characters)
  4. Mentions - Count full length including @ symbol
  5. Hashtags - Count full length including # symbol

Example:

const text = 'Check out https://example.com 👋 #awesome';
// Character count breakdown:
// "Check out " = 10
// "https://example.com" = 23 (not actual length!)
// " " = 1
// "👋" = 2 (emoji)
// " " = 1
// "#awesome" = 8
// Total = 45 characters

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 TwitterContentValidator, TwitterContentBuilder, and TwitterContentTransformer 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: Integrated

Justification: This package provides NeverHub service discovery integration through the neverhub-registration.ts module. Content type packages 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: Service discovery integration - package registers content type with NeverHub for runtime discovery.

Example Integration:

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

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

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.

Dependencies

  • validator - URL and data validation utilities
  • @bernierllc/crypto-utils - For URL shortening hash generation (future use)

Related Packages

License

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

This code is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.