Progress sync
Report a reading position back to the site, sync the library, and use the correct shape for progress data.
Suwatte has no tracker of its own. A source gives it one. A source that reads a site with accounts can keep the list on that site current.
Four capabilities do this work. Write any of them, or none.
getContentProgress(id)
This method tells the app the position that the site holds. It returns
ContentProgressData or null.
async getContentProgress(id: string): Promise<ContentProgressData | null> {
const entry = await this.fetchListEntry(id);
if (!entry) return null;
return {
readChapters: entry.readChapters,
status: TrackStatus.READING,
lastReadDate: entry.updatedAt,
};
}
The fields
| Field | Use it when |
|---|---|
readChapters |
The read chapters are not in one continuous block. Give a number[] or a Set<number>. |
progress |
The person read each chapter up to one number. Give that number. |
status |
The list status of the title. |
lastReadDate |
The date of the last read. |
The order of precedence
The app decides in this sequence:
- If
readChaptersis there and is not empty, the app uses it. It wins. - If not, and
progressis there, the app marks each chapter with anumbervalue that is less than or equal toprogress.
The sync only adds. The app does not use this data to mark a chapter as unread. A person who read a chapter keeps that state.
Two examples:
// The history has gaps.
return { readChapters: [1, 2, 5, 8], status: TrackStatus.READING };
// The person read each chapter up to 42.
return { progress: 42, status: TrackStatus.READING };
The progress events
Write any of these methods, and the app tells your source as a person reads:
| Method | When the app calls it |
|---|---|
onChaptersMarked |
A person marks chapters as read or unread. |
onChapterProgress |
The position in a chapter changes. |
onPageRead |
A person reads a page. |
onPageRead happens often. Collect the events and send them together. Do not make one request for
each page.
Use these methods to send the position up:
async onChaptersMarked(contentId, chapterIds, completed) {
if (!completed) return;
await this.client.post("/list/progress", {
id: contentId,
chapter: Math.max(...chapterIds.map(this.chapterNumber)),
});
}
The tracker form
getTrackEntryPage(id) returns a form. A person uses it to change the
list entry: the status, the score and the progress.
async getTrackEntryPage(id: string): Promise<UIForm> {
const entry = await this.fetchListEntry(id);
return {
sections: [
{
header: "Tracking",
views: [
{
type: "picker",
id: "status",
title: "Status",
currentValue: entry.status,
options: [
{ id: "reading", title: "Reading" },
{ id: "completed", title: "Completed" },
{ id: "planning", title: "Planning" },
],
},
{
type: "stepper",
id: "progress",
title: "Chapters read",
currentValue: entry.progress,
lowerBound: 0,
step: 1,
},
],
},
],
};
}
The app sends the result to onFormSubmitted.
Library sync
synchronizeLibrary(since, added, removed) reconciles the whole library with the site.
async synchronizeLibrary(since, added, removed): Promise<LibrarySyncResponse> {
await this.pushChanges(added, removed);
const remote = await this.fetchListSince(since);
return { full: remote.map(toItem) };
}
The app calls it on the interval in
Settings → Sources → Source Updates → Library Sync Interval. Use since to get only what
changed.
Three events tell you about the library as it changes:
onContentsAddedToLibrary(ids)onContentsRemovedFromLibrary(ids)onContentsReadingFlagChanged(ids, flag)
Declare your trackers
Declare the trackers that you know:
getConfiguration(): SourceConfiguration {
return { endpoint: ["mal", "anilist"] };
}
The app matches these names against the endpoints map from
getContent. The same values find
Linked Titles in a library. This is the most useful thing that you can do for a
person who reads from more than one source.
Incognito Mode
With Incognito Mode on, the app does not record progress. Do not try to go around it.