@bernierllc/social-media-mastodon
v1.0.0
Published
Mastodon API integration service with OAuth 2.0, content posting (toots, threads, polls), media uploads, and decentralized federation support
Readme
@bernierllc/social-media-mastodon
Mastodon API integration service with OAuth 2.0, content posting (toots, threads, polls), media uploads, and decentralized federation support.
Features
- OAuth 2.0 Authentication: Secure authentication flow with automatic app registration
- Content Posting: Post statuses (toots), threads, and scheduled posts
- Media Management: Upload images with focal points and alt text
- Federation Support: Works with any Mastodon instance
- Instance Configuration: Fetch instance-specific settings and limits
- Social Interactions: Favorite, boost, and reply to statuses
- TypeScript Support: Full type definitions included
Installation
npm install @bernierllc/social-media-mastodonQuick Start
Basic Setup
import { MastodonService } from '@bernierllc/social-media-mastodon';
const mastodon = new MastodonService({
instanceUrl: 'https://mastodon.social',
appRegistration: {
autoRegister: true,
appName: 'My Awesome App',
scopes: ['read', 'write'],
},
});Authentication Flow
// 1. Register your app (if not already registered)
const registration = await mastodon.registerApp();
console.log('Client ID:', registration.clientId);
// 2. Get authorization URL
const authUrl = await mastodon.getAuthorizationUrl(
'https://mastodon.social',
'https://myapp.com/callback'
);
console.log('Visit this URL to authorize:', authUrl);
// 3. Handle the callback with the authorization code
const result = await mastodon.handleCallback(authorizationCode);
if (result.success) {
console.log('Authenticated!');
console.log('Access Token:', result.credentials?.accessToken);
}Posting Content
// Simple status
const post = await mastodon.postStatus({
status: 'Hello Mastodon!',
visibility: 'public',
});
console.log('Posted:', post.url);
// Status with content warning
await mastodon.postStatus({
status: 'This is sensitive content',
spoilerText: 'Warning: Spoilers',
sensitive: true,
visibility: 'unlisted',
});
// Status with media
const media = await mastodon.uploadMedia({
file: Buffer.from(imageData),
mimeType: 'image/jpeg',
description: 'A beautiful sunset',
focus: { x: 0.5, y: 0.75 }, // Focal point
});
if (media.success) {
await mastodon.postStatus({
status: 'Check out this photo!',
mediaIds: [media.mediaId!],
});
}Posting Threads
const thread = await mastodon.postThread([
{ status: 'First post in the thread' },
{ status: 'Second post replying to the first' },
{ status: 'Third post completing the thread' },
]);
if (thread.success) {
console.log('Thread posted with IDs:', thread.statusIds);
}Scheduling Posts
const scheduledTime = new Date('2025-12-31T12:00:00Z');
const scheduled = await mastodon.scheduleStatus(
{
status: 'Happy New Year!',
visibility: 'public',
},
scheduledTime
);
console.log('Scheduled for:', scheduled.scheduledFor);Social Interactions
// Favorite (like) a status
await mastodon.favouriteStatus('status-id-123');
// Boost (reblog) a status
await mastodon.boostStatus('status-id-456');
// Reply to a status
await mastodon.replyToStatus('status-id-789', {
status: '@username This is my reply',
});
// Delete a status
await mastodon.deleteStatus('status-id-abc');Instance Configuration
// Fetch instance information
const config = await mastodon.fetchInstanceConfig('https://mastodon.social');
console.log('Instance:', config.title);
console.log('Max characters:', config.maxTootChars);
console.log('Version:', config.version);
console.log('Supported media types:', config.supportedMimeTypes);API Reference
MastodonService
Constructor
new MastodonService(config: MastodonServiceConfig)Parameters:
config.instanceUrl(string, required): Mastodon instance URLconfig.clientId(string, optional): Pre-registered app client IDconfig.clientSecret(string, optional): Pre-registered app client secretconfig.appRegistration(object, optional): App registration settings
Methods
Authentication
registerApp(instanceUrl?: string): Promise<MastodonAppRegistration>
Register your application with a Mastodon instance.
getAuthorizationUrl(instanceUrl: string, callbackUrl: string): Promise<string>
Generate OAuth authorization URL for user authentication.
handleCallback(code: string, instanceUrl?: string): Promise<MastodonAuthResult>
Handle OAuth callback and exchange authorization code for access token.
isAuthenticated(): boolean
Check if the service is authenticated.
Instance Information
fetchInstanceConfig(instanceUrl?: string): Promise<MastodonInstanceConfig>
Fetch instance configuration and limits.
getInstanceConfig(): MastodonInstanceConfig | null
Get cached instance configuration.
Content Posting
postStatus(content: MastodonContent): Promise<MastodonPostResult>
Post a single status (toot).
postThread(contents: MastodonContent[]): Promise<MastodonThreadResult>
Post a thread of connected statuses.
scheduleStatus(content: MastodonContent, scheduledTime: Date): Promise<MastodonScheduleResult>
Schedule a status for future posting.
deleteStatus(statusId: string): Promise<boolean>
Delete a status.
replyToStatus(statusId: string, content: MastodonContent): Promise<MastodonPostResult>
Reply to an existing status.
Media
uploadMedia(media: MastodonMedia): Promise<MastodonMediaUploadResult>
Upload media (image, video, etc.).
updateMediaDescription(mediaId: string, description: string): Promise<void>
Update media alt text/description.
setMediaFocus(mediaId: string, focus: MastodonFocalPoint): Promise<void>
Set media focal point for cropping.
Social Interactions
favouriteStatus(statusId: string): Promise<boolean>
Favorite (like) a status.
boostStatus(statusId: string): Promise<boolean>
Boost (reblog) a status.
Types
MastodonContent
interface MastodonContent {
status: string; // Status text
spoilerText?: string; // Content warning
visibility?: MastodonVisibility; // 'public' | 'unlisted' | 'private' | 'direct'
inReplyToId?: string; // Reply to status ID
mediaIds?: string[]; // Attached media IDs
sensitive?: boolean; // Mark as sensitive content
language?: string; // Language code (e.g., 'en')
scheduledAt?: Date; // Schedule for future posting
}MastodonMedia
interface MastodonMedia {
file: Buffer | string; // File buffer or path
mimeType: string; // MIME type (e.g., 'image/jpeg')
description?: string; // Alt text
focus?: MastodonFocalPoint; // Focal point for cropping
}MastodonFocalPoint
interface MastodonFocalPoint {
x: number; // X coordinate (0.0 to 1.0)
y: number; // Y coordinate (0.0 to 1.0)
}Error Handling
All async methods return result objects with success boolean and optional error message:
const result = await mastodon.postStatus({ status: 'Hello!' });
if (!result.success) {
console.error('Failed to post:', result.error);
} else {
console.log('Posted successfully:', result.statusId);
}Best Practices
1. Check Instance Limits
const config = await mastodon.fetchInstanceConfig();
if (myStatus.length > config.maxTootChars) {
console.warn('Status too long for this instance!');
}2. Use Content Warnings
await mastodon.postStatus({
status: 'Spoiler content here',
spoilerText: 'Warning: Movie spoilers',
sensitive: true,
});3. Add Alt Text to Images
const media = await mastodon.uploadMedia({
file: imageBuffer,
mimeType: 'image/jpeg',
description: 'A detailed description for screen readers',
});4. Handle Authentication State
if (!mastodon.isAuthenticated()) {
// Redirect to authorization flow
const authUrl = await mastodon.getAuthorizationUrl(...);
// ... redirect user
}5. Respect Rate Limits
Mastodon instances have rate limits. Consider implementing retry logic with exponential backoff.
Multi-Instance Support
The service supports multiple Mastodon instances. Each instance requires separate app registration:
// Register on multiple instances
const mastodonSocial = new MastodonService({
instanceUrl: 'https://mastodon.social',
});
const fosstodon = new MastodonService({
instanceUrl: 'https://fosstodon.org',
});
await mastodonSocial.registerApp();
await fosstodon.registerApp();Testing
This package includes comprehensive tests with >90% coverage.
npm testLicense
Copyright (c) 2025 Bernier LLC. See LICENSE file for details.
Contributing
This package is part of the BernierLLC tools monorepo. For bug reports and feature requests, please use the issue tracker.
Related Packages
@bernierllc/social-media-twitter- Twitter/X API integration@bernierllc/social-media-bluesky- Bluesky API integration@bernierllc/social-publisher- Multi-platform social media publisher
Support
For support and questions, please refer to the main repository documentation.
