Unofficial Paperbnd/Popfeed plugin for KOReader
1# Paperbnd KOReader Plugin - Developer Documentation
2
3> **Document Purpose**: This is technical documentation for developers and maintainers. For user-facing installation and usage instructions, see [README.md](README.md).
4
5A KOReader plugin for syncing reading progress to an AT Protocol PDS using the Popfeed List Item lexicon.
6
7## Overview
8
9Paperbnd allows KOReader users to sync their reading progress to their AT Protocol Personal Data Server (PDS) via the Popfeed social reading platform. The plugin tracks page progress and synchronizes it with existing Popfeed book entries.
10
11## Architecture
12
13The plugin consists of two main components:
14
15### 1. XRPC Client (`xrpc.lua`)
16
17A dedicated module for AT Protocol/XRPC communication that handles:
18
19- **Generic XRPC method invocation** with support for GET and POST requests
20- **PDS resolution** via the Slingshot service (Microcosm)
21- **Advanced session management** with multi-level token renewal and automatic retry
22- **Authentication** using handle and app password
23- **Record operations** (list, get, put) for AT Protocol collections
24
25**Key Features:**
26- Table-based parameters for flexibility (method, params, body, pds, skip_renewal)
27- Multi-level session renewal: refresh token → app password fallback → automatic retry
28- Session validation on plugin startup to proactively renew expired tokens
29- Authorization headers only on POST requests (GET requests never authenticated)
30- Callback support for persisting refreshed tokens to plugin settings
31- Handles both 401 and 400 "expired" errors as authentication failures
32- Prevents infinite recursion during renewal with `skip_renewal` flag
33
34### 2. Main Plugin (`main.lua`)
35
36The primary plugin file that integrates with KOReader:
37
38- **Settings management** with persistent storage of credentials and book mappings
39- **Authentication flow** with two-step credential input (handle, then app password)
40- **Book linking** allows users to select from their existing Popfeed list items
41- **Progress sync** tracks current page, total pages, and percentage
42- **Document hooks** for automatic sync on document close
43- **Menu integration** with conditional menu items based on authentication state
44
45## User Workflow
46
471. **Set Credentials**: User enters their AT Protocol handle and app password
48 - Plugin resolves PDS URL via Slingshot service
49 - Creates session and stores access/refresh tokens
50
512. **Link Book**: User selects current document and links it to an existing Popfeed book
52 - Fetches user's list items from their PDS
53 - Filters for books only
54 - Stores mapping between document path and list item rkey
55
563. **Sync Progress**: Reading progress is synchronized automatically
57 - Updates `bookProgress` field with current page, total pages, and percentage
58 - Updates `listType` to "currently_reading_books" if needed
59 - Can be triggered manually or automatically on document close
60
614. **Session Management**: Tokens are automatically renewed with multi-level fallback
62 - Session validated at plugin startup to ensure fresh tokens
63 - Authentication errors (401 or 400 "expired") trigger automatic renewal
64 - First attempts refresh with refresh token
65 - Falls back to creating new session with app password if refresh token expired
66 - Original request automatically retried after successful renewal
67 - New tokens saved to settings transparently via callback
68 - No user intervention required at any point
69
70## Technical Details
71
72### External Resources
73
74- **KOReader Plugin Development**: https://github.com/koreader/koreader/wiki/Developer-documentation
75- **AT Protocol Specifications**: https://atproto.com/specs/atp
76- **Popfeed Platform**: https://popfeed.social/
77- **Paperbnd Website**: https://paperbnd.club/
78
79### Lexicons Used
80
81- `social.popfeed.feed.listItem` - Individual book entries with progress tracking
82- `social.popfeed.feed.list` - Reading lists (e.g., "currently_reading_books")
83- `com.atproto.server.createSession` - Initial authentication
84- `com.atproto.server.refreshSession` - Token refresh
85- `com.atproto.repo.listRecords` - Fetch book collections
86- `com.atproto.repo.getRecord` - Fetch individual book records
87- `com.atproto.repo.putRecord` - Update book records with progress
88
89### Data Storage
90
91Settings are persisted in `paperbnd.lua` within KOReader's settings directory:
92- Handle and app password
93- PDS URL
94- Access and refresh tokens
95- User DID (Decentralized Identifier)
96- Document-to-listItem mappings (document path -> {rkey, title, author})
97
98### Book Progress Format
99
100```lua
101bookProgress = {
102 status = "in_progress",
103 percent = 42, -- Calculated percentage
104 currentPage = 150, -- From KOReader document stats
105 totalPages = 357, -- From KOReader document stats
106 updatedAt = "2025-10-20T14:30:00.000Z" -- ISO 8601 timestamp
107}
108```
109
110## Design Decisions
111
112### Simplicity First
113- Single-file XRPC client for clean separation of concerns
114- No future feature speculation - only implemented what's needed
115- Direct and straightforward code without unnecessary abstractions
116
117### User Book Selection
118Rather than attempting to match books by ISBN or title, users manually link documents to existing Popfeed list items. This approach:
119- Avoids complex ISBN DB API integration
120- Ensures accuracy (user confirms the correct book)
121- Works with any book format supported by KOReader
122- Leverages existing Popfeed records as the source of truth
123
124### Advanced Session Management
125
126The plugin implements a sophisticated multi-level session management system that is the core reliability feature.
127
128**Architecture:**
129
130The session management system has three levels of components:
131
1321. **`call()` method in xrpc.lua**: Generic XRPC request handler that detects auth errors (401 or 400 "expired"), calls `renewSession()` on failure, rebuilds requests with new tokens, and retries automatically. This is the single point of entry for all XRPC operations.
133
1342. **`renewSession()` method**: Multi-level fallback coordinator that first attempts `refreshSession()`, then falls back to `createSession()` with app password if refresh token expired. Returns boolean success status.
135
1363. **`validateSession()` method**: Makes lightweight test request at plugin startup to verify token validity and proactively renew expired tokens before user operations.
137
138**Integration with Main Plugin:**
139
140- **Initialization**: Loads credentials from persistent storage, configures XRPC client with tokens and app password, and sets callback for automatic token persistence
141- **Token Callback**: Receives new tokens from XRPC client, saves to plugin settings immediately, ensuring credentials never get out of sync
142- **Startup Validation**: Runs `validateSession()` silently when plugin loads to ensure tokens are fresh before first use
143
144**Automatic Renewal Flow:**
145
1461. Authentication error detected (401 or 400 "expired")
1472. Attempt to refresh using refresh token
1483. If refresh token expired, create new session with stored app password
1494. If renewal successful, retry the original request automatically
1505. Save new tokens to persistent storage via callback
151
152**Example Flow - User syncs progress with expired access token:**
153
1541. `syncProgress()` calls `xrpc:putRecord()`
1552. `putRecord()` calls `call()` with POST body
1563. Server returns 401 Unauthorized
1574. `call()` detects auth error, calls `renewSession()`
1585. `renewSession()` tries `refreshSession()`
1596. If refresh token valid: new tokens received, callback fired
1607. If refresh token expired: `createSession()` with app password
1618. `call()` rebuilds request with new access token
1629. `call()` retries POST request
16310. Request succeeds, progress synced
16411. User sees success message, unaware of token renewal
165
166**Benefits:**
167
168- No manual "Refresh Session" button needed
169- Tokens can expire for weeks without user intervention
170- Original requests automatically retried after renewal
171- Seamless user experience without interruptions
172- Callback mechanism keeps plugin and XRPC client in sync
173- Proactive validation prevents mid-operation failures
174
175### Authentication Strategy
176- Uses Slingshot service to resolve PDS from handle (no manual PDS entry)
177- App passwords for security (no main account passwords stored)
178- Refresh tokens enable long-lived sessions without re-authentication
179
180## Implementation Notes
181
182### Reference Implementation
183Built by studying the ReadwiseReader KOReader plugin for:
184- Plugin structure and initialization patterns
185- UI component creation (menus, dialogs, input forms)
186- HTTP request handling with `socket.http` and `ltn12`
187- JSON encoding/decoding with `rapidjson`
188- Settings persistence with `LuaSettings`
189- Document metadata access and event hooks
190
191### AT Protocol Specifics
192- GET requests never require authentication headers
193- POST requests use Bearer token authentication
194- Refresh tokens are used as access tokens for the refresh endpoint
195- Both access and refresh tokens are rotated on each refresh
196- Session validation uses `com.atproto.server.getSession` endpoint
197- Authentication errors can return 401 or 400 with "expired" message
198
199### Error Handling
200- User-friendly error messages via `InfoMessage` widgets
201- Graceful degradation when operations fail
202- Network errors handled with appropriate fallbacks
203
204## Code Quality Assessment
205
206**Current State (December 2024):**
207
208The codebase is well-structured and production-ready with the following characteristics:
209
210**Strengths:**
211- Clean separation of concerns (XRPC client vs. plugin logic)
212- Comprehensive error handling with user-friendly messages
213- Robust session management with multiple fallback mechanisms
214- Proper state synchronization between components via callbacks
215- No memory leaks or blocking operations
216- Defensive programming with validation at critical points
217- Clear function naming and logical organization
218
219**Architecture:**
220- **XRPC Client (353 lines)**: Self-contained networking layer with 15 public methods
221- **Main Plugin (446 lines)**: UI integration with 17 public methods
222- **Metadata (6 lines)**: KOReader plugin manifest
223- **Total**: 805 lines of well-documented Lua code
224
225**Known Limitations:**
226- One TODO in `main.lua` for updating `listUri` when changing reading status (currently commented out at lines 413-416). Implementation would require fetching the user's "currently_reading_books" list URI and updating the `listUri` field when moving a book to that list.
227- Session validation at startup runs synchronously (could be async but startup is fast enough)
228- No retry logic for network failures (only authentication failures)
229- No offline queuing (by design, for simplicity)
230
231**Testing Considerations:**
232- Manual testing required (KOReader plugin environment)
233- Test scenarios: expired access token, expired refresh token, network errors
234- Edge cases: malformed responses, missing credentials, unlinked books
235
236## Future Enhancements (Not Implemented)
237
238Potential features intentionally left out for simplicity:
239
240- **Automatic book detection via ISBN**: Query ISBN databases to automatically match books instead of manual linking
241- **Batch syncing multiple books**: Sync progress for all linked books at once
242- **Conflict resolution for concurrent edits**: Handle cases where book data is modified from multiple clients
243- **Offline queue for syncing**: Queue progress updates when network unavailable and sync when connection restored
244- **Automatic list URI updates**: When changing a book's reading status (e.g., to "currently_reading_books"), automatically fetch and update the `listUri` field to point to the correct list. Currently commented out in code - would require additional `listRecords` call to fetch the user's lists and find the matching URI.
245- **Retry logic for transient network failures**: Automatically retry failed requests due to temporary network issues
246- **Progress indicators for long-running operations**: Show loading spinners or progress bars for network requests
247
248## Files
249
250- `main.lua` - Main plugin with UI and KOReader integration (446 lines)
251- `xrpc.lua` - XRPC client for AT Protocol communication (353 lines)
252- `_meta.lua` - KOReader plugin manifest (6 lines)
253- `README.md` - User-facing documentation with installation and usage instructions
254- `CLAUDE.md` - This technical documentation for developers and maintainers
255
256## Troubleshooting
257
258### Common Issues
259
260**"Failed to resolve PDS" error**
261- Verify handle is correct (e.g., "user.bsky.social")
262- Check internet connection
263- Confirm Slingshot service (slingshot.microcosm.blue) is accessible
264
265**"Authentication failed" error**
266- Ensure app password is correct (not your main account password)
267- Create a new app password if needed from your account settings
268- Verify handle format matches your AT Protocol identifier
269
270**"Failed to fetch books: No books found"**
271- Books must be added to Popfeed/Paperbnd first before linking
272- Books must be in a "currently_reading_books" list
273- Use the Popfeed website or app to add books to your account
274
275**"Failed to sync progress" error**
276- Check that the book is still linked (may have been unlinked)
277- Verify internet connection
278- Token renewal should happen automatically, but if persisting, try re-entering credentials
279
280**Book not appearing in link list**
281- Ensure book has `creativeWorkType` set to "book"
282- Verify book is in "currently_reading_books" list type
283- Try unlinking and re-linking the book
284
285### Debug Tips
286
287- Check KOReader logs for detailed error messages
288- Verify credentials are saved by checking settings file at `<KOReader settings dir>/paperbnd.lua`
289- Test network connectivity with other KOReader plugins
290- Ensure document has page count metadata (required for progress percentage)
291
292## Development
293
294The plugin was developed collaboratively with Claude (Anthropic's AI assistant) with these priorities:
295- Clean, readable code
296- Comprehensive error handling
297- User experience focused on simplicity
298- No emojis in documentation or messages
299- Direct communication style in code comments
300- Proactive reliability through startup validation