@akomalabs/kemono
Version:
A production-grade TypeScript API wrapper for the Kemono/Coomer platforms
337 lines (272 loc) • 8.8 kB
Markdown
# @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
```bash
bun add @akomalabs/kemono
```
## Quick Start
```typescript
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
```typescript
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:
```typescript
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
```typescript
// List all creators
const creators = await client.creators.listAll();
// Get a creator's profile
const profile = await client.creators.getProfile(service, creatorId);
```
### Posts API
```typescript
// 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.
```typescript
// 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:
```typescript
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
```bash
# Install dependencies
bun install
# Run tests
bun test
# Build package
bun run build
```
## License
MIT License
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.