Files
MonsterCards/docs/Import-Export.md
2026-09-08 13:58:35 -07:00

276 lines
20 KiB
Markdown

# Import & Export Architecture & Plan
This document outlines the modular architecture and step-by-step technical roadmap for handling monster stat block imports, exports, and sharing across multiple formats in the **Monster Cards** application.
---
## 1. File Formats & Importer Architecture
Monster Cards separates canonical native formats from third-party import sources:
1. **Universal Native Format (`.card` / `.card.txt`)**:
- Our native file format utilizing the **Universal 2-Tier Envelope Specification** (`$schema`, `uuid`, `ruleset_id`, `entity_type`, `display_name`, `template_id`, `properties`).
- Uses the **Open5e Ruleset** (`ruleset_id: "open5e"`) which matches our app's 5e monster and character needs ([`entities/character.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/entities/character.json)).
- Supported for both **Export** and **Native Import**.
2. **Third-Party Import-Only Formats**:
- `.monster` / `.monster.txt`: Tetra-cube generator stat block JSON format (Import-only).
- D&D Beyond URLs / API payloads: D&D Beyond character web service format (Import-only).
All format parsers implement a shared interface:
```java
public interface EntityImporter<T> {
/**
* Determines whether the given input string or payload can be handled by this importer.
*/
boolean canImport(@NonNull String input);
/**
* Parses the raw input string into internal domain memory objects (e.g., Monster).
*/
@NonNull
T parse(@NonNull String input) throws Exception;
}
```
The output of any `EntityImporter` is an in-memory domain model (such as `Monster`), which is then passed to `MonsterImportFragment` for UI preview and Room database persistence.
---
## 2. Step-by-Step Implementation Roadmap
```
┌────────────────----------------────────────────────────┐
│ Step 0: Restrict File Extension Intent Filters │
└────────────────────────────────────────────────────────┘
│
v
┌────────────────----------------────────────────────────┐
│ Step 1: Refactor Import Code to Shared Interface │
└────────────────────────────────────────────────────────┘
│
v
┌────────────────----------------────────────────────────┐
│ Step 2: Update Tetra-cube Importer to Newest Format │
└────────────────────────────────────────────────────────┘
│
v
┌────────────────----------------────────────────────────┐
│ Step 3: Import from D&D Beyond Character URL │
└────────────────────────────────────────────────────────┘
│
v
┌────────────────----------------────────────────────────┐
│ Step 4: Export to Custom Internal Format (Open5e) │
└────────────────────────────────────────────────────────┘
│
v
┌────────────────----------------────────────────────────┐
│ Step 5: Import from Custom Internal Format (Open5e) │
└────────────────────────────────────────────────────────┘
│
v
┌────────────────----------------────────────────────────┐
│ Step 6: Generic Android Share Button Feature │
└────────────────────────────────────────────────────────┘
│
v
┌────────────────----------------────────────────────────┐
│ Step 7: Specific Share Targets (NFC, Bluetooth, URL) │
└────────────────────────────────────────────────────────┘
```
---
### Step 0: File Extension Intent Filters (.monster & .card)
Restrict the Android app so it reacts to supported stat block file extensions (`.monster`, `.monster.txt` for Tetra-cube format; `.card`, `.card.txt` for internal Open5e card format) instead of all generic `.txt` files.
1. **`AndroidManifest.xml`**:
Add `android:pathPattern` constraints (`.monster`, `.monster.txt`, `.card`, `.card.txt`) to the `<intent-filter>` for `MainActivity`.
2. **`MainActivity.java`**:
Implement runtime display name validation querying `OpenableColumns.DISPLAY_NAME` via `ContentResolver` to filter out non-monster files passed via `content://` URIs.
---
### Step 1: Refactor Import Code into Importer Architecture
Extract the current parsing logic out of `MonsterImportHelper.java` into a standalone importer class implementing `EntityImporter<Monster>`.
- **New Class**: `TetraCubeMonsterImporter implements EntityImporter<Monster>`
- **Responsibilities**:
- `canImport(json)`: Validates that the JSON string contains Tetra-cube properties (e.g. `hitDice`, `armorName`, `strPoints`).
- `parse(json)`: Converts the raw JSON payload into an in-memory `Monster` object.
---
### Step 2: Update Tetra-cube Importer to Newest Format
Update `TetraCubeMonsterImporter` to support newer fields from the Tetra-cube generator source (`js/statblock-script.js`):
- **New Fields to Parse**:
- `bonusActions`: Array of `{ name, desc }` objects $\rightarrow$ mapped to `monster.actions` or bonus action structure.
- `mythics` / `isMythic` / `mythicDescription`: Array of mythic action objects and intro text $\rightarrow$ mapped to `monster.legendaryActions`.
- `blind`: Parse boolean flag to append `"(blind beyond this radius)"` to `blindsight`.
- Section intro text: Parse `legendariesDescription`, `lairDescription`, `regionalDescription`.
---
### Step 3: Import from D&D Beyond URL & Web Services
Allow users to import characters, monsters, and future entity types directly from D&D Beyond URLs using a multi-channel ingestion architecture.
- **Primary Target Share Links**:
- Site Share Link: `https://www.dndbeyond.com/characters/49074997/s7sLyX` (primary link copied when tapping "Share" on D&D Beyond; contains numeric character ID `49074997` and share token `s7sLyX`).
- Standard Web Link: `https://www.dndbeyond.com/characters/49074997` (HTML page that loads background JavaScript to fetch stats).
- URL Extractor Regex: `https?://(?:www\.)?dndbeyond\.com/characters/(\d+)(?:/([a-zA-Z0-9]+))?`
- **URL Ingestion Channels**:
1. **Android Share Sheet (`ACTION_SEND`)**:
- Register `intent-filter` for `ACTION_SEND` with `text/plain` in `AndroidManifest.xml`.
- Tapping "Share" on D&D Beyond passes `https://www.dndbeyond.com/characters/49074997/s7sLyX` directly into Monster Cards.
2. **Android Web Link Chooser (`ACTION_VIEW`)**:
- Register `intent-filter` for `ACTION_VIEW` in `AndroidManifest.xml` with scheme `https`, host `www.dndbeyond.com`, and path prefixes `/characters/`, `/monsters/`, `/spells/`, etc.
- Allows Monster Cards to appear in the Android "Open with..." link chooser dialog.
3. **In-App Menu & Clipboard Auto-Detection**:
- Add an "Import from URL..." menu item in `LibraryFragment` and `MonsterImportFragment`.
- Displays a URL input dialog with automatic clipboard URL detection (`ClipboardManager`).
- **Known Endpoints & Web Asset Structure**:
- **Main Character Service Endpoint**:
- `https://character-service.dndbeyond.com/character/v5/character/49074997?includeCustomItems=true`
- Returns JSON payload containing full character statistics, attributes, modifiers, classes, race, inventory, and spells.
- Full reference sample JSON payload extracted to: [`docs/dndbeyond-character-sample.json`](file:///Users/tom/Projects/Apps/MonsterCards/docs/dndbeyond-character-sample.json).
- **Auxiliary Service Endpoints**:
- Vehicles: `https://character-service.dndbeyond.com/character/v5/vehicles?characterId=49074997`
- Vehicle Components: `https://character-service.dndbeyond.com/character/v5/vehicle/components?characterId=49074997`
- Known Infusions: `https://character-service.dndbeyond.com/character/v5/known-infusions?characterId=49074997`
- Infusion Items: `https://character-service.dndbeyond.com/character/v5/infusion/items?characterId=49074997`
- **Image & Avatar Assets**:
- Default Builder Avatar: `https://www.dndbeyond.com/Content/Skins/Waterdeep/images/characters/default-avatar-builder.png`
- Race Portrait Avatar: `portraitAvatarUrl` (e.g., `https://www.dndbeyond.com/avatars/2489/881/636680412207671648.jpeg`)
- Item / Attunement Icons: Returned in `inventory[].definition.avatarUrl` (e.g. `https://www.dndbeyond.com/avatars/19/144/636382339478303209.jpeg`, `https://www.dndbeyond.com/avatars/9249/564/637203446409923453.jpeg`).
- **Analytics / Telemetry**:
- `https://global.ketchcdn.com/web/v2/log...` (returns 204 No Content; user consent log endpoint ignored by importer).
- **Architecture (`DnDBeyondImporter implements EntityImporter<Monster>`)**:
- **URL Extractor**: Extracts `{characterId}` (`49074997`) from shared URLs (`https://www.dndbeyond.com/characters/49074997/s7sLyX`), trimming optional share tokens.
- **HTTP Service Client**: Performs an asynchronous HTTP GET request (via OkHttp/Retrofit) to `character-service.dndbeyond.com/character/v5/character/{characterId}?includeCustomItems=true`.
- **Domain Mapper**: Maps D&D Beyond JSON fields (stats, modifiers, AC, HP, speed, proficiencies, actions, bonus actions, reactions, spells, traits, avatar URLs) into the internal `Monster` domain model (`sourceUrl = "https://www.dndbeyond.com/characters/49074997/s7sLyX"`).
- **UI Preview**: Passes the generated `Monster` object to `MonsterImportFragment` for review and persistence.
---
### Step 4: Export to Custom Internal Format (Open5e Specification)
Implement export capability to output monsters and collections using our two-tier Open5e JSON specification (detailed in [`/Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/import-export.md`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/import-export.md)).
- **New Class**: `Open5eExporter`
#### Open5e Ruleset Architecture & Schema Specification
The Open5e ruleset is part of a modular, versioned ruleset framework located at [`/Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/). In the future, this ruleset will be embedded directly into the application assets and versioned as our canonical model reference.
1. **Ruleset & Entity Identifiers**:
- Every ruleset defines a short string ID (`ruleset_id: "open5e"`) and a unique UUID (`uuid: "7a35e4d2-f67b-4890-a292-6a7593c72b21"`), declared in the ruleset manifest [`manifest.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/manifest.json).
- Exported entities contain top-level identifiers (`uuid`, `ruleset_id`, `entity_type`, `display_name`) wrapped in a universal envelope.
- When importing a JSON payload like [`examples/character/goblin.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/examples/character/goblin.json), importers inspect `ruleset_id == "open5e"` to match the registered ruleset parser.
2. **Base Schemas ([`/Users/tom/Projects/TTRPG/CharacterDataFiles/schema/*.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/schema/))**:
- **[`schema/entity.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/schema/entity.json)**: The universal envelope schema wrapping all exported entities (`uuid`, `ruleset_id`, `entity_type`, `display_name`, `description`, `version`, `tags`, `template_id`, `properties`).
- **[`schema/manifest.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/schema/manifest.json)**: Schema defining ruleset manifest metadata, registered entity types, and sheet templates.
- **[`schema/entity-definition.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/schema/entity-definition.json)**: Meta-schema describing how entity types and property schemas are declared.
3. **Character Entity Schema ([`entities/character.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/entities/character.json))**:
- Defines the `properties` payload for Player Characters (PCs), NPCs, and Monsters/Creatures.
- Core fields in `properties`:
- **Identity & Vitals**: `size`, `type`, `subtype`, `alignment`, `armor_class`, `armor_description`, `hit_points` (`current`, `max`, `formula`, `hit_dice`), `speed`, `speed_desc`, `challenge_rating`, `cr`.
- **Ability Scores**: `abilities` (`strength`, `dexterity`, `constitution`, `intelligence`, `wisdom`, `charisma`).
- **Proficiencies & Senses**: `saving_throws`, `skills`, `senses`, `languages`.
- **Actions & Traits**: `traits`, `actions`, `reactions`, `legendary_actions`, `lair_actions`, `regional_effects`, and `spellcasting`.
4. **Rendering Templates (`template_id`)**:
- The `template_id` field in the universal envelope specifies how the entity should be rendered in external or host applications.
- Registered templates in [`manifest.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/manifest.json):
- `"stat_block"`: Monster Manual style stat block (`templates/stat_block.html`).
- `"character_card"`: Compact NPC summary card (`templates/character_card.html`).
- `"character_list_item"`: Fixed-height quick-reference list item (`templates/character_list_item.html`).
- `"character_sheet"` / `"character_sheet_alt"`: Full 5e interactive character sheets.
#### Exporter Output Requirements (`Open5eExporter`)
- **Envelope Generation**:
- Emits `$schema: "../../../schema/entity.json"`.
- Populates `uuid` (random UUID v4), `ruleset_id: "open5e"`, `entity_type: "character"`, `display_name: monster.name`, `template_id: "stat_block"`.
- **Payload Mapping**:
- Maps `Monster` fields to Open5e character properties in `properties` object conforming to [`entities/character.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/entities/character.json).
#### Open5e vs. Monster Cards Database Schema Comparison & Final Mappings
- **Database Storage Architecture (Room SQLite vs. Open5e JSON)**:
- **Ability Scores**: Kept as flat integer columns in Room (`strength_score`, `dexterity_score`, etc.) for maximum query performance. When exporting to Open5e JSON, `Open5eExporter` packages these into a `properties.abilities` JSON object `{ "strength": 12, ... }`. Modifiers are calculated dynamically using `floor((score - 10) / 2)`.
- **Variable-Length Lists (`traits`, `actions`, `skills`, `languages`)**: Stored as text columns using Room `@TypeConverter` JSON converters. In Open5e JSON export, traits and actions are formatted as `{ "name": "...", "desc": "..." }` arrays.
- **PC Identity Fields**: Added PC identity columns (`player_name`, `background`, `personality_traits`, `ideals`, `bonds`, `flaws`, `age`, `height`, `weight`, `eyes`, `skin`, `hair`, `appearance`, `backstory`, `allies_and_organizations`) directly to `Monster.java` (`Database version 7`, `MIGRATION_6_7`).
- **Omitted PC Fields**: `currency`, `inventory`, `death_saves`, and `spellcasting.slots` are ignored/omitted in monster stat block exports.
---
### Step 5: Import from Custom Internal Format (Open5e Specification)
Implement import capability for custom Open5e JSON files.
- **New Class**: `Open5eImporter implements EntityImporter<Monster>`
- **Pipeline**:
- `canImport(json)`: Validates `"ruleset_id": "open5e"` and `"entity_type": "character"`.
- `parse(json)`: Unpacks `properties` into internal `Monster` domain models.
---
### Step 6: Generic Android Share Button Feature
Implement a generic "Share" feature allowing users to export and share monsters or collections from detail screens.
- **UI Action**: Add a "Share" item to action menus in `MonsterDetailFragment` and `CollectionDetailFragment`.
- **Android Intent**: Uses standard Android `ACTION_SEND` intent with `Intent.EXTRA_STREAM` or `Intent.EXTRA_TEXT` to pass exported files to other apps (e.g. Email, Drive, Messaging, Files).
---
### Step 7: Specific Share Targets & Channels
Extend the generic sharing feature with specialized, direct sharing channels (to be implemented as individual sub-steps):
- **Sub-Step 7.1 (NFC Sharing)**: Share monster data directly between devices via NFC (NDEF records / Android Beam).
- **Sub-Step 7.2 (Bluetooth / Wi-Fi Direct)**: Share monster files directly between nearby Android devices via Bluetooth / Wi-Fi.
- **Sub-Step 7.3 (Web URL with Embedded Payload)**: Generate a shareable Web URL containing compressed/base64-encoded monster JSON data.
---
## 3. Implementation Checklist & Status
- [x] **Step 0**: Restrict app intent filters in `AndroidManifest.xml` (`.monster` & `.monster.txt`) and add runtime filename validation in `MainActivity.java`.
- [x] **Step 1**: Refactor import & conversion code into a shared `EntityImporter<T>` interface and `TetraCubeMonsterImporter` class.
- [x] **Step 2**: Update Tetra-cube importer class to support the newest Tetra-cube format (`bonusActions`, `mythics`, `blind`, intro descriptions).
- [x] **Step 3**: Import from D&D Beyond URL (`https://www.dndbeyond.com/characters/49074997` fetching from v5 character service endpoint).
- [x] **Step 4**: Export to internal format described by Open5e document (`Open5eExporter`).
- [x] **Step 5**: Import from internal format described by Open5e document (`Open5eImporter`).
- [x] **Step 6**: Generic Android Share button feature (`ACTION_SEND`).
- [ ] **Step 7**: Specific share targets (7.1 NFC, 7.2 Bluetooth, 7.3 Embedded Web URL).
---
## 4. Schema Planning & Text Formatting Notes
### 4.1 Schema Architecture Considerations
- **Unified Action Entity / Type Column**:
- Instead of maintaining separate `List<Trait>` columns for each category (`actions`, `reactions`, `legendaryActions`, `lairActions`, `regionalActions`, etc.), a unified `monster_actions` table (or model list) with an `action_type` column (`ABILITY`, `ACTION`, `BONUS_ACTION`, `REACTION`, `LEGENDARY_ACTION`, `MYTHIC_ACTION`, `LAIR_ACTION`, `REGIONAL_EFFECT`) simplifies Room DB queries and enables a single reusable editor UI component.
- **Section Intro & End Note Metadata**:
- Store section-level intro text and end notes (e.g. `legendaryActionsDescription`, `lairActionsDescription`, `lairActionsEndNote`, `regionalActionsDescription`, `regionalActionsEndNote`, `mythicActionsDescription`) as dedicated metadata fields on the `Monster` entity rather than as artificial action traits.
### 4.2 Markdown & Content Formatting Support
- **Supported Markdown Syntax**:
- **Emphasis**: Italics (`_text_` or `*text*`) and Bold (`__text__` or `**text**`).
- **Lists**: Bulleted lists (`- `, `* `) and Numbered lists (`1. `, `2. `).
- **External Links**: Standard markdown links `[Text](https://...)`.
- **Internal Links (Future Deep Linking)**: Custom URI scheme `[Label](mc://<type>/<id>)` (e.g., `[Fireball](mc://spell/fireball)`) reserved for in-app navigation.
- **Data Model & Import/Export Handling**:
- All text fields across domain models, Room persistence, and import/export payloads store raw CommonMark markdown strings.
- Importers preserve raw markdown syntax as exported by source formats (Tetra-cube, Open5e, D&D Beyond).
- UI rendering layers handle conversion from CommonMark to Android Spanned text at display time.