Reading Cards
Load character cards from PNG images, JSON files, or CHARX containers. The library auto-detects format and normalizes everything to V3.
Prerequisites
Before starting, you should:
Overview
We'll cover:
- Loading cards with auto-detection
- Using format-specific functions
- Reading standalone lorebooks
- Handling read options
Step 1: Load a Card with Auto-Detection
The simplest approach - let readCard() figure out the format:
import { readCard } from '@motioneffector/cards'
import { readFileSync } from 'fs'
// Works for PNG, JSON, or CHARX
const bytes = readFileSync('character.png')
const card = readCard(bytes)
console.log(card.data.name)
For JSON stored as a string:
const jsonString = '{"spec":"chara_card_v3",...}'
const card = readCard(jsonString)
The function examines the input to determine format:
Uint8Arraystarting with PNG signature → PNGUint8Arraystarting withPK→ CHARX (ZIP)string→ JSON
Step 2: Use Format-Specific Functions
When you know the format, use the specific function for clearer code:
import {
readCardFromPng,
readCardFromJson,
readCardFromCharx
} from '@motioneffector/cards'
// PNG file
const pngCard = readCardFromPng(pngBytes)
// JSON string
const jsonCard = readCardFromJson(jsonString)
// CHARX file
const charxCard = readCardFromCharx(charxBytes)
These throw ParseError if the format doesn't match.
Step 3: Read Standalone Lorebooks
Lorebooks can exist as separate files (not embedded in a card):
import { readLorebook } from '@motioneffector/cards'
// From PNG (NovelAI-style lorebook image)
const lorebook = readLorebook(lorebookPngBytes)
// From JSON
const lorebook = readLorebook(lorebookJsonString)
console.log(`Entries: ${lorebook.entries.length}`)
Standalone lorebooks use the Lorebook type, not wrapped in a card structure.
Step 4: Configure Read Options
Control parsing behavior with options:
const card = readCard(bytes, {
strict: true, // Throw on invalid data instead of best-effort parsing
parseDecorators: true, // Parse @@decorators in lorebook entries (default: true)
})
Strict Mode
By default, the library is permissive - it tries to read whatever it can. Strict mode throws errors on:
- Invalid CRC checksums in PNG chunks
- Unrecognized card formats
import { readCard, ParseError } from '@motioneffector/cards'
try {
const card = readCard(bytes, { strict: true })
} catch (error) {
if (error instanceof ParseError) {
console.error('Card is malformed:', error.message)
}
}
Disable Decorator Parsing
If you want raw lorebook content without parsing @@ decorators:
const card = readCard(bytes, { parseDecorators: false })
// entry.content will include the @@decorator lines
// entry.decorators will be undefined
Complete Example
import { readCard, ParseError } from '@motioneffector/cards'
import { readFileSync } from 'fs'
function loadCard(filePath: string) {
const bytes = readFileSync(filePath)
try {
const card = readCard(bytes)
console.log(`Loaded: ${card.data.name}`)
console.log(`Creator: ${card.data.creator || 'Unknown'}`)
console.log(`Tags: ${card.data.tags.join(', ') || 'None'}`)
if (card.data.character_book) {
console.log(`Lorebook entries: ${card.data.character_book.entries.length}`)
}
return card
} catch (error) {
if (error instanceof ParseError) {
console.error(`Failed to parse ${filePath}: ${error.message}`)
}
throw error
}
}
Variations
Reading from URL (Browser)
async function loadCardFromUrl(url: string) {
const response = await fetch(url)
const bytes = new Uint8Array(await response.arrayBuffer())
return readCard(bytes)
}
Reading from Base64
import { readCard, decodeBase64 } from '@motioneffector/cards'
const base64String = 'iVBORw0KGgo...'
const bytes = decodeBase64(base64String)
const card = readCard(bytes)
Batch Reading
import { readCard } from '@motioneffector/cards'
import { readFileSync, readdirSync } from 'fs'
import { join } from 'path'
function loadAllCards(directory: string) {
const files = readdirSync(directory).filter(f => f.endsWith('.png'))
return files.map(file => {
const bytes = readFileSync(join(directory, file))
try {
return { file, card: readCard(bytes) }
} catch {
return { file, card: null, error: 'Failed to parse' }
}
})
}
Troubleshooting
ParseError: No character card data found
Symptom: Reading a PNG throws "No character card data found in PNG"
Cause: The PNG doesn't contain embedded card data - it's just a regular image.
Solution: Verify the file is actually a character card, not just an image of a character.
ParseError: Invalid JSON
Symptom: Reading throws "Invalid JSON"
Cause: The file contents aren't valid JSON, or the base64 decoding produced garbage.
Solution: For JSON files, check for syntax errors. For PNG files, the embedded data may be corrupted - try repairCard().
Unexpected V1/V2 Fields
Symptom: A card parsed from an old source has empty V3 fields.
Cause: Normal behavior - V1/V2 cards don't have V3 fields, so they're set to defaults.
Solution: This is expected. Check for undefined/empty before using optional fields.
See Also
- File Formats - How cards are stored in different formats
- Validation & Repair - Handling corrupted cards
- Reading API - Full function reference