@akomalabs/kemono
v0.1.2
Published
A production-grade TypeScript API wrapper for the Kemono/Coomer platforms
Downloads
65
Maintainers
Readme
@akomalabs/kemono
A production-grade TypeScript API wrapper for the Kemono/Coomer platforms with advanced features like schema validation, caching, and rate limiting.
Features
- 🚀 Full TypeScript support with comprehensive type definitions
- ✅ Zod schema validation for type safety
- 🔄 Built-in caching with in-memory and Redis support
- 🛡️ Configurable rate limiting
- 📝 Detailed logging options
- ⚡ Bun-optimized for maximum performance
- 🔒 Session management for favorites
- 🌐 Support for multiple base URLs (kemono.su and coomer.su)
Installation
bun add @akomalabs/kemonoQuick Start
import { KemonoClient } from '@akomalabs/kemono';
// Create a client instance
const client = new KemonoClient({
baseUrl: 'https://kemono.su/api/v1', // or 'https://coomer.su/api/v1'
cache: {
enabled: true,
ttl: 300, // 5 minutes
},
});
// Fetch a creator's profile
const profile = await client.creators.getProfile('onlyfans', 'creatorId');
// Get recent posts
const posts = await client.posts.listRecent();
// Get posts from a specific creator
const creatorPosts = await client.posts.getByCreator('onlyfans', 'creatorId');
// Get a specific post with attachments
const post = await client.posts.getPost('onlyfans', 'creatorId', 'postId');
const attachments = await client.posts.getAttachments('onlyfans', 'creatorId', 'postId');Type System
The library uses Zod for runtime type validation and TypeScript for static typing. All types are inferred from Zod schemas, ensuring runtime type safety.
Available Types and Schemas
import {
// Schemas (for runtime validation)
KemonoBaseUrlSchema,
KemonoServiceSchema,
KemonoConfigSchema,
FileSchema,
AttachmentSchema,
PostSchema,
CreatorSchema,
SearchParamsSchema,
// Types (for static typing)
type KemonoBaseUrl,
type KemonoService,
type KemonoConfig,
type File,
type Attachment,
type Post,
type Creator,
type SearchParams,
// Error types
KemonoApiError,
AuthenticationError,
NotFoundError,
ValidationError,
RateLimitError,
} from '@akomalabs/kemono';
// Example: Using schemas for runtime validation
const config = KemonoConfigSchema.parse({
baseUrl: 'https://kemono.su/api/v1',
cache: { enabled: true },
});
// Example: Using types for static typing
const post: Post = {
id: '123',
title: 'Example Post',
content: 'Hello World',
// ...other required fields
};Configuration
The client accepts a comprehensive configuration object that is validated at runtime:
interface KemonoConfig {
// Base URL for API requests
baseUrl?: 'https://kemono.su/api/v1' | 'https://coomer.su/api/v1';
// Session key for favorites management
sessionKey?: string;
// Caching configuration
cache?: {
enabled: boolean;
ttl?: number;
redis?: {
host: string;
port: number;
password?: string;
};
};
// Rate limiting configuration
rateLimit?: {
maxRequests: number;
windowMs: number;
};
// Logging configuration
logging?: {
enabled: boolean;
level?: 'error' | 'warn' | 'info' | 'debug';
};
}API Reference
Creators API
// List all creators
const creators = await client.creators.listAll();
// Get a creator's profile
const profile = await client.creators.getProfile(service, creatorId);Posts API
// List recent posts
const posts = await client.posts.listRecent({
q?: string; // Search query
o?: number; // Offset
limit?: number; // Number of posts to return
});
// Get posts by creator
const posts = await client.posts.getByCreator(service, creatorId);
// Get a specific post
const post = await client.posts.getPost(service, creatorId, postId);
// Get post attachments
const attachments = await client.posts.getAttachments(service, creatorId, postId);Favorites API
The Favorites API requires a valid session key to be provided in the client configuration. All favorites methods will throw an AuthenticationError if the session key is missing or invalid.
// Initialize client with session key
const client = new KemonoClient({
baseUrl: 'https://kemono.su/api/v1',
sessionKey: 'your-session-key', // Required for favorites
});
// List favorite creators with error handling
try {
const creators = await client.favorites.listCreators();
console.log(`Found ${creators.length} favorite creators`);
// Access creator properties
creators.forEach(creator => {
console.log(`${creator.name} (${creator.service})`);
console.log(`Last imported: ${creator.last_imported}`);
console.log(`Favorite sequence: ${creator.faved_seq}`);
});
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Please provide a valid session key');
} else {
console.error('Failed to fetch favorite creators:', error);
}
}
// List favorite posts with pagination example
try {
const posts = await client.favorites.listPosts();
console.log(`Found ${posts.length} favorite posts`);
// Access post properties
posts.forEach(post => {
console.log(`${post.title} by ${post.user}`);
console.log(`Published: ${post.published}`);
console.log(`Content: ${post.content.substring(0, 100)}...`);
// Handle attachments
if (post.attachments.length > 0) {
console.log('Attachments:', post.attachments.map(a => a.name).join(', '));
}
});
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Please provide a valid session key');
} else {
console.error('Failed to fetch favorite posts:', error);
}
}
// Managing favorites
async function manageFavorites() {
try {
// Add a creator to favorites
await client.favorites.addCreator('fanbox', 'creator123');
// Add a post to favorites
await client.favorites.addPost('fanbox', 'creator123', 'post456');
// Remove from favorites
await client.favorites.removeCreator('fanbox', 'creator123');
await client.favorites.removePost('fanbox', 'creator123', 'post456');
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Session key required');
} else if (error instanceof NotFoundError) {
console.error('Creator or post not found');
} else if (error instanceof RateLimitError) {
console.error('Too many requests, please try again later');
} else {
console.error('Operation failed:', error);
}
}
}
// Response Types
interface FavoriteCreator {
id: string; // Creator's unique identifier
name: string; // Creator's display name
service: KemonoService; // Platform (e.g., 'fanbox', 'patreon')
indexed: string; // ISO date when the creator was indexed
updated: string; // ISO date of last update
faved_seq: number; // Sequence number in favorites
last_imported: string; // ISO date of last import
public_id: string | null; // Public identifier if available
relation_id: string | null; // Related identifier if available
}
interface FavoritePost {
id: string; // Post unique identifier
user: string; // Creator's ID
service: KemonoService; // Platform service
title: string; // Post title
content: string; // Post content
added: string; // ISO date when added to favorites
published: string; // ISO date when published
edited: string | null; // ISO date of last edit
file: { // Main file if present
name: string; // File name
path: string; // File path
};
attachments: any[]; // Additional files
embed: Record<string, any>; // Embedded content
shared_file: boolean; // Indicates if file is shared
faved_seq: number; // Sequence number in favorites
}Supported Services
- fanbox
- patreon
- gumroad
- subscribestar
- dlsite
- fantia
- onlyfans
- fansly
Error Handling
The client includes custom error classes for better error handling:
try {
await client.creators.getProfile('onlyfans', 'nonexistent');
} catch (error) {
if (error instanceof NotFoundError) {
// Handle 404 error
} else if (error instanceof AuthenticationError) {
// Handle authentication error
} else if (error instanceof ValidationError) {
// Handle validation errors (e.g., invalid config)
} else if (error instanceof RateLimitError) {
// Handle rate limit errors
} else if (error instanceof KemonoApiError) {
// Handle other API errors
}
}Development
# Install dependencies
bun install
# Run tests
bun test
# Build package
bun run buildLicense
MIT License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
