### Initiate Readwise Export Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Starts a new export process by calling the initialization endpoint. Requires authentication headers and client versioning. ```typescript // API Endpoint: GET https://readwise.io/api/obsidian/init // Query Parameters: // - parentPageDeleted: boolean - indicates if base folder was deleted // - statusID: number (optional) - resume from specific export // - auto: boolean (optional) - indicates automatic vs manual sync // Response interface: interface ExportRequestResponse { latest_id: number; // Export status ID for polling status: string; // Current status } // Example request: const parentDeleted = !await app.vault.adapter.exists(settings.readwiseDir); const url = `https://readwise.io/api/obsidian/init?parentPageDeleted=${parentDeleted}`; const response = await fetch(url, { headers: { 'AUTHORIZATION': `Token ${settings.token}`, 'Obsidian-Client': clientId, 'Readwise-Client-Version': '3.0.2', } }); // Response status codes: // 200 - Data already synced on another device // 201 - New export queued, poll for status // 409 - Sync in progress from different client // 417 - Export locked, wait an hour ``` -------------------------------- ### Export Initialization API Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Initiates a new export from Readwise, which queues highlight data for download. This endpoint can be used to start the export process and obtain an initial status ID for subsequent polling. ```APIDOC ## GET /api/obsidian/init ### Description Initiates a new export from Readwise, which queues highlight data for download. ### Method GET ### Endpoint https://readwise.io/api/obsidian/init ### Query Parameters - **parentPageDeleted** (boolean) - Required - indicates if base folder was deleted - **statusID** (number) - Optional - resume from specific export - **auto** (boolean) - Optional - indicates automatic vs manual sync ### Request Example ```typescript const parentDeleted = !await app.vault.adapter.exists(settings.readwiseDir); const url = `https://readwise.io/api/obsidian/init?parentPageDeleted=${parentDeleted}`; const response = await fetch(url, { headers: { 'AUTHORIZATION': `Token ${settings.token}`, 'Obsidian-Client': clientId, 'Readwise-Client-Version': '3.0.2', } }); ``` ### Response #### Success Response (200) - **latest_id** (number) - Export status ID for polling - **status** (string) - Current status #### Success Response (201) - **latest_id** (number) - Export status ID for polling - **status** (string) - Current status #### Error Response (409) - Description: Sync in progress from different client #### Error Response (417) - Description: Export locked, wait an hour ``` -------------------------------- ### Trigger Manual and Programmatic Sync Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Demonstrates how to trigger synchronization processes programmatically within the plugin, including options for specific book IDs or automatic flags. ```typescript // Command registered in Obsidian - accessible via Command Palette (Ctrl/Cmd+P) // Command ID: readwise-official-sync // Command Name: "Sync your data now" // The sync process: // 1. Checks if sync is already in progress // 2. Initiates export request to Readwise API // 3. Polls for export status until complete // 4. Downloads and extracts ZIP archive with highlights // 5. Writes markdown files to configured readwiseDir // Usage via Command Palette: // 1. Press Ctrl/Cmd+P to open Command Palette // 2. Type "Readwise" to filter commands // 3. Select "Readwise Official: Sync your data now" // Programmatic usage within the plugin: await plugin.syncBookHighlights(); // With specific book IDs to refresh: await plugin.syncBookHighlights(["book-id-1", "book-id-2"]); // Automatic sync (triggered by schedule or on-load): await plugin.syncBookHighlights(undefined, true); // auto=true ``` -------------------------------- ### POST /api/obsidian/sync_ack Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Confirms successful sync completion to the Readwise server. ```APIDOC ## POST https://readwise.io/api/obsidian/sync_ack ### Description Confirms successful sync completion to the Readwise server. ### Method POST ### Endpoint https://readwise.io/api/obsidian/sync_ack ### Parameters #### Query Parameters - **statusID** (number) - Optional - Specific export to acknowledge ### Request Example POST https://readwise.io/api/obsidian/sync_ack?statusID=123 ``` -------------------------------- ### Configure Readwise Plugin Settings Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Defines the interface and default values for plugin configuration, including sync frequency and storage directory. ```typescript // Plugin settings interface - configure these in Obsidian settings interface ReadwisePluginSettings { token: string; // Readwise API authentication token readwiseDir: string; // Folder to save highlights (default: "Readwise") frequency: string; // Auto-sync interval: "0" (manual), "60" (hourly), "720" (12h), "1440" (24h), "10080" (weekly) triggerOnLoad: boolean; // Automatically sync when Obsidian opens refreshBooks: boolean; // Re-sync deleted files on next sync reimportShowConfirmation: boolean; // Show confirmation dialog for reimport } // Default configuration const DEFAULT_SETTINGS = { token: "", readwiseDir: "Readwise", frequency: "0", // Manual sync by default triggerOnLoad: true, refreshBooks: false, reimportShowConfirmation: true }; ``` -------------------------------- ### Download and Extract Artifacts Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Downloads ZIP archives and processes highlight data files, including logic for merging content into existing local files. ```typescript // API Endpoint: GET https://readwise.io/api/v2/download_artifact/{artifactId} // Returns: ZIP blob containing JSON files with highlight data // JSON file structure in ZIP: interface HighlightData { book_id?: string; // Readwise book identifier reader_document_id?: string; // Reader document identifier full_content?: string; // Complete markdown content append_only_content?: string; // Content to append to existing file last_content_hash?: string; // MD5 hash for change detection full_document_text?: string; // Full document (for Reader articles) full_document_text_path?: string; // Path for full document last_full_document_hash?: string; // Hash for full document } // Extraction process: async function downloadArtifact(artifactId: number) { const response = await fetch( `https://readwise.io/api/v2/download_artifact/${artifactId}`, { headers: getAuthHeaders() } ); const blob = await response.blob(); const zipReader = new zip.ZipReader(new zip.BlobReader(blob)); const entries = await zipReader.getEntries(); for (const entry of entries) { // Convert path: Readwise/Books/Title.json -> {readwiseDir}/Books/Title.md const processedFileName = normalizePath( entry.filename .replace(/^Readwise/, settings.readwiseDir) .replace(/\.json$/, ".md") ); const fileContent = await entry.getData(new zip.TextWriter()); const data = JSON.parse(fileContent); // Handle content merging for existing files if (await fs.exists(processedFileName)) { const existingContent = await fs.read(processedFileName); const existingHash = Md5.hashStr(existingContent).toString(); if (existingHash !== data.last_content_hash) { // File was modified locally - append new content contentToSave = existingContent.trimEnd() + "\n" + data.append_only_content; } } await fs.write(processedFileName, contentToSave); } await zipReader.close(); } ``` -------------------------------- ### Customize Formatting Command Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Opens the Readwise website for customizing export templates and formatting options. This command provides access to Jinja2 templates, source selection, metadata fields, and file naming conventions. ```typescript // Command ID: readwise-official-format // Command Name: "Customize formatting" // Opens external URL for template customization: // URL: https://readwise.io/export/obsidian/preferences // Available customization options (via web interface): // - Jinja2 templates for highlight formatting // - Which sources to export (Kindle, Pocket, etc.) // - Metadata fields to include // - File naming conventions // - Folder organization structure ``` -------------------------------- ### Implement Readwise API Authentication Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Handles client ID generation and sets up authorization headers for API requests. ```typescript // Authentication flow - opens browser for Readwise authorization // API Endpoint: GET https://readwise.io/api_auth?token={uuid}&service=obsidian // Step 1: Generate unique client ID (stored in localStorage) function getObsidianClientID(): string { let obsidianClientId = window.localStorage.getItem('rw-ObsidianClientId'); if (!obsidianClientId) { obsidianClientId = Math.random().toString(36).substring(2, 15); window.localStorage.setItem('rw-ObsidianClientId', obsidianClientId); } return obsidianClientId; } // Step 2: Poll for user access token after browser auth // API Endpoint: GET https://readwise.io/api/auth?token={uuid} // Response: { userAccessToken: string } // Authentication headers used for all API requests: const headers = { 'AUTHORIZATION': `Token ${settings.token}`, 'Obsidian-Client': getObsidianClientID(), 'Readwise-Client-Version': '3.0.2', }; ``` -------------------------------- ### Artifact Download API Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Downloads ZIP archives containing highlight markdown files and extracts them to the vault. This endpoint retrieves the actual data after the export process is complete. ```APIDOC ## GET /api/v2/download_artifact/{artifactId} ### Description Downloads ZIP archives containing highlight markdown files and extracts them to the vault. ### Method GET ### Endpoint https://readwise.io/api/v2/download_artifact/{artifactId} ### Path Parameters - **artifactId** (number) - Required - The ID of the artifact to download. ### Response #### Success Response - Returns: ZIP blob containing JSON files with highlight data. ### JSON File Structure in ZIP - **book_id** (string) - Optional - Readwise book identifier - **reader_document_id** (string) - Optional - Reader document identifier - **full_content** (string) - Optional - Complete markdown content - **append_only_content** (string) - Optional - Content to append to existing file - **last_content_hash** (string) - Optional - MD5 hash for change detection - **full_document_text** (string) - Optional - Full document (for Reader articles) - **full_document_text_path** (string) - Optional - Path for full document - **last_full_document_hash** (string) - Optional - Hash for full document ### Extraction Process Example ```typescript async function downloadArtifact(artifactId: number) { const response = await fetch( `https://readwise.io/api/v2/download_artifact/${artifactId}`, { headers: getAuthHeaders() } ); const blob = await response.blob(); const zipReader = new zip.ZipReader(new zip.BlobReader(blob)); const entries = await zipReader.getEntries(); for (const entry of entries) { const processedFileName = normalizePath( entry.filename .replace(/^Readwise/, settings.readwiseDir) .replace(/\.json$/, ".md") ); const fileContent = await entry.getData(new zip.TextWriter()); const data = JSON.parse(fileContent); let contentToSave = data.full_content || data.append_only_content; if (await fs.exists(processedFileName)) { const existingContent = await fs.read(processedFileName); const existingHash = Md5.hashStr(existingContent).toString(); if (existingHash !== data.last_content_hash) { contentToSave = existingContent.trimEnd() + "\n" + data.append_only_content; } } await fs.write(processedFileName, contentToSave); } await zipReader.close(); } ``` ``` -------------------------------- ### Handle File Deletions in Obsidian Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Queues deleted files for re-synchronization if configured and updates the book ID mapping. Requires settings and saveSettings to be available. ```typescript // Delete handler - queues deleted files for re-sync app.vault.on("delete", async (file) => { const bookId = settings.booksIDsMap[file.path]; if (bookId && settings.refreshBooks) { settings.booksToRefresh.push(bookId); } delete settings.booksIDsMap[file.path]; await saveSettings(); }); ``` -------------------------------- ### Reimport File Command Logic Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Implements the 'Delete and reimport this document' command. This function deletes the local file, updates settings to queue a refresh, and triggers a sync for the specific book. ```typescript // Command ID: readwise-official-reimport-file // Command Name: "Delete and reimport this document" // Only available when viewing a Readwise-synced file // Reimport process: async function reimportFile(vault: Vault, fileName: string) { // Delete the local file await vault.delete(vault.getAbstractFileByPath(fileName)); // Get the book ID from internal mapping const bookId = settings.booksIDsMap[fileName]; // Add to refresh queue settings.booksToRefresh.push(bookId); await saveSettings(); // Trigger sync for this specific book await syncBookHighlights([bookId]); } // The command checks if current file is a Readwise file: const activeFilePath = app.workspace.getActiveFile()?.path; const isRWfile = activeFilePath && activeFilePath in settings.booksIDsMap; ``` -------------------------------- ### Process Hyper Highlights in Markdown Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt A markdown post-processor that styles text enclosed in double underscores (__like this__) with a specific CSS class for rendered views. It only processes files within the configured Readwise directory. ```typescript // Markdown post-processor for Readwise hyper highlights // Converts __highlighted text__ to styled spans in rendered view registerMarkdownPostProcessor((el, ctx) => { // Only process files in Readwise directory if (!ctx.sourcePath.startsWith(settings.readwiseDir)) return; // Find all __text__ patterns in source const matches = [...ctx.getSectionInfo(el).text.matchAll(/__(.+)__/g)] .map((a) => a[1]); // Replace elements with styled spans const hypers = el.findAll("strong") .filter(e => matches.includes(e.textContent)); hypers.forEach(strongEl => { const replacement = el.createEl('span'); while (strongEl.firstChild) { replacement.appendChild(strongEl.firstChild); } replacement.addClass("rw-hyper-highlight"); strongEl.replaceWith(replacement); }); }); // CSS class .rw-hyper-highlight can be styled in styles.css ``` -------------------------------- ### Refresh Book Export API Request Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Use this endpoint to request specific books to be included in the next export, useful for re-syncing deleted or failed files. Ensure correct Content-Type and authentication headers are included. ```typescript // API Endpoint: POST https://readwise.io/api/refresh_book_export // Content-Type: application/json // Request body: interface RefreshBookRequest { exportTarget: 'obsidian'; userBookIds: string[]; // Readwise book IDs readerDocumentIds: string[]; // Reader document IDs (prefixed internally) } // Usage for re-syncing specific books: const response = await fetch('https://readwise.io/api/refresh_book_export', { method: 'POST', headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' }, body: JSON.stringify({ exportTarget: 'obsidian', userBookIds: ['12345', '67890'], readerDocumentIds: ['abc123'] }) }); // Reader document IDs are encoded with prefix for internal tracking: function encodeReaderDocumentId(rawId: string): string { return `readerdocument:${rawId}`; } function decodeReaderDocumentId(encodedId: string): string { return encodedId.replace(/^readerdocument:/, ""); } ``` -------------------------------- ### POST /api/refresh_book_export Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Requests specific books to be included in the next export, used for re-syncing deleted or failed files. ```APIDOC ## POST https://readwise.io/api/refresh_book_export ### Description Requests specific books to be included in the next export, used for re-syncing deleted or failed files. ### Method POST ### Endpoint https://readwise.io/api/refresh_book_export ### Request Body - **exportTarget** (string) - Required - Must be 'obsidian' - **userBookIds** (array) - Required - Readwise book IDs - **readerDocumentIds** (array) - Required - Reader document IDs (prefixed with 'readerdocument:') ### Request Example { "exportTarget": "obsidian", "userBookIds": ["12345", "67890"], "readerDocumentIds": ["readerdocument:abc123"] } ``` -------------------------------- ### StatusBar Component for Sync Status Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Manages the display of sync status messages in Obsidian's status bar. Includes logic to prevent duplicate messages and force immediate display updates. ```typescript // StatusBar class for displaying sync progress class StatusBar { private messages: StatusBarMessage[] = []; private currentMessage: StatusBarMessage; private statusBarEl: HTMLElement; displayMessage(message: string, timeout: number, forcing: boolean = false) { // Prevents duplicate consecutive messages if (this.messages[0]?.message === message) return; this.messages.push({ message: `readwise: ${message.slice(0, 100)}`, timeout: timeout * 1000, }); if (forcing) { // Clear current message immediately this.currentMessage = null; this.statusBarEl.setText(""); } this.display(); } } // Usage in plugin: this.statusBar.displayMessage("syncing...", 35, true); this.statusBar.displayMessage("sync completed", 4, false); ``` -------------------------------- ### Handle File Renames in Obsidian Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Updates the internal book ID mapping when a file is renamed to maintain data integrity. Ensure settings and saveSettings are properly initialized. ```typescript // Rename handler - updates internal book ID mapping app.vault.on("rename", async (file, oldPath) => { const bookId = settings.booksIDsMap[oldPath]; if (bookId) { delete settings.booksIDsMap[oldPath]; settings.booksIDsMap[file.path] = bookId; await saveSettings(); } }); ``` -------------------------------- ### Sync Acknowledgment API Request Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Confirms successful sync completion to the Readwise server. This endpoint can optionally accept a statusID for acknowledging a specific export. ```typescript // API Endpoint: POST https://readwise.io/api/obsidian/sync_ack // Query Parameters: // - statusID: number (optional) - specific export to acknowledge // Called after successful artifact download and extraction: async function acknowledgeSyncCompleted(statusID?: number) { const url = statusID ? `https://readwise.io/api/obsidian/sync_ack?statusID=${statusID}` : `https://readwise.io/api/obsidian/sync_ack`; await fetch(url, { method: 'POST', headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' } }); } ``` -------------------------------- ### Export Status Polling API Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Polls the export status to track progress and retrieve artifact IDs for download. This endpoint is used to monitor the export process initiated by the initialization API. ```APIDOC ## GET /api/get_export_status ### Description Polls the export status to track progress and retrieve artifact IDs for download. ### Method GET ### Endpoint https://readwise.io/api/get_export_status ### Query Parameters - **exportStatusId** (number) - Required - The ID obtained from the export initialization. ### Response #### Success Response - **totalBooks** (number) - Total books to export - **booksExported** (number) - Books exported so far - **isFinished** (boolean) - Export complete flag - **taskStatus** (string) - PENDING | RECEIVED | STARTED | RETRY | SUCCESS - **artifactIds** (number[]) - IDs of downloadable ZIP artifacts ### Request Example ```typescript async function getExportStatus(statusID: number) { const response = await fetch( `https://readwise.io/api/get_export_status?exportStatusId=${statusID}`, { headers: getAuthHeaders() } ); const data = await response.json(); if (['PENDING', 'RECEIVED', 'STARTED', 'RETRY'].includes(data.taskStatus)) { console.log(`Progress: ${data.booksExported} / ${data.totalBooks}`); for (const artifactId of data.artifactIds) { await downloadArtifact(artifactId); } await new Promise(resolve => setTimeout(resolve, 1000)); await getExportStatus(statusID); } else if (data.taskStatus === 'SUCCESS') { await acknowledgeSyncCompleted(statusID); } } ``` ``` -------------------------------- ### Poll Export Status Source: https://context7.com/readwiseio/obsidian-readwise/llms.txt Tracks the progress of a queued export and retrieves artifact IDs once the task status reaches success. ```typescript // API Endpoint: GET https://readwise.io/api/get_export_status?exportStatusId={id} // Response interface: interface ExportStatusResponse { totalBooks: number; // Total books to export booksExported: number; // Books exported so far isFinished: boolean; // Export complete flag taskStatus: string; // PENDING | RECEIVED | STARTED | RETRY | SUCCESS artifactIds: number[]; // IDs of downloadable ZIP artifacts } // Polling implementation with progress updates: async function getExportStatus(statusID: number) { const response = await fetch( `https://readwise.io/api/get_export_status?exportStatusId=${statusID}`, { headers: getAuthHeaders() } ); const data = await response.json(); if (['PENDING', 'RECEIVED', 'STARTED', 'RETRY'].includes(data.taskStatus)) { // Show progress: "Exporting Readwise data (50 / 100) ..." console.log(`Progress: ${data.booksExported} / ${data.totalBooks}`); // Download any available artifacts while waiting for (const artifactId of data.artifactIds) { await downloadArtifact(artifactId); } // Wait 1 second and poll again await new Promise(resolve => setTimeout(resolve, 1000)); await getExportStatus(statusID); } else if (data.taskStatus === 'SUCCESS') { // Download remaining artifacts and complete sync await acknowledgeSyncCompleted(statusID); } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.