Download

Write a source

Optional capabilities

A source adds an interface for each thing its site can do beyond the basics, and Katari shows the matching feature only for sources that have it.

What each one adds #

interface ConfigurableSource : UnifiedSource
A settings screen for the source, as in Settings.

Implement

fun setupPreferenceScreen(screen: EntryPreferenceScreen)
interface RelatedEntriesSource : UnifiedSource
Related entries on a title's page, in the order returned. They can be of any kind, so set each one's type.

Implement

suspend fun getRelatedEntries(entry: SEntry): List<RelatedEntryGroup>
Each RelatedEntryGroup(title, entries) shows as a row of covers under its title, such as Sequels. If the site doesn't say how titles relate, return one group with a null title.
interface ResolvableSource : UnifiedSource
Addresses on the site open as the title or chapter they point to, as in Opening links.

Implement

fun getUriType(uri: String): EntryUriType
suspend fun getEntry(uri: String): SEntry?
suspend fun getChapter(uri: String): SEntryChapter?
interface MergedEntrySource : UnifiedSource
A title made of other titles of the source, such as a show of several seasons. See Merged entries.

Implement

suspend fun getMergedEntries(entry: SEntry): List<SEntry>
interface DeferredStreamSource : UnifiedSource
Video links that take extra requests to find, looked up only for the stream about to play. See Links found later.

Implement

suspend fun resolveStream(chapter: SEntryChapter, stream: VideoStream): VideoStream
interface EntryPreviewSource : UnifiedSource
Still images of a video title, such as frames from a film, in its Preview, which the user opens by pressing and holding it in a list. Previews show on Android only for now.

Implement

suspend fun getEntryPreview(entry: SEntry): List<EntryPreviewImage>
Each image is an EntryPreviewImage(index, imageUrl, title = null, url = null).
interface ChapterListPartsSource : UnifiedSource
A long chapter list read in parts, the newest first and the older ones in the background. See Long chapter lists.

Implement

suspend fun getOlderChapters(entry: SEntry, token: String): ChapterListPart
interface ArtworkSource : UnifiedSource
Covers and chapter stills loaded with the source's client and headers, for a host that serves images only with the site's Referer or cookies. EntryHttpSource and EntryImageSource already have it; without it Katari loads them with its own client and no source headers.

Implement

val client: OkHttpClient
val headers: Headers
interface SiteStorageSource : UnifiedSource
The site's web storage, for a site that keeps a login or other state in localStorage or sessionStorage, which unlike cookies Katari's client never sees. While the user browses the source's site in WebView, Katari reads the page's storage after each page load and hands over the last reading when WebView closes.

Implement

fun saveSiteStorage(storage: SiteStorage)
Called on the main thread, so keep it short, such as saving what you need in the source's settings. SiteStorage holds the page's url, which may be another site the user went on to, such as a login provider, and its local and session maps.
interface ChapterWebViewSource : WebViewSource
WebView for a single chapter. EntryImageHttpSource already has it.

Implement

fun getContentUrl(entry: SEntry): String
fun getChapterUrl(chapter: SEntryChapter): String
interface ResumableEntryImageSource : EntryImageSource
Comic page downloads that pick up where they stopped: Katari asks for the rest with a Range header and keeps what it has only when the host answers 206 Partial Content. A marker; there is nothing to implement.
interface EntryItemOrientationProvider
Wide covers in lists with HORIZONTAL, for sites whose thumbnails are landscape.

Implement

val itemOrientation: EntryItemOrientation
VERTICAL unless overridden.
interface UnmeteredSource
No warning for this source when a library update holds more than 60 of its titles, or comic downloads queue more than 30 chapters or 15 from it. For sources that can take the load, such as your own server. A marker; there is nothing to implement.
interface ChapterNumberRecognitionSource
Katari reads chapter numbers from the names of chapters whose chapterNumber is -1. A marker; there is nothing to implement.
interface EmptyChapterListSource
An empty chapter list is accepted rather than treated as an error. A marker; there is nothing to implement.

The immersive view, which plays comics and videos one after another, needs none of these: it works with every source. Books don't take part, and a source whose supportedEntryTypes has neither MANGA nor ANIME doesn't offer it.

Settings #

Settings are AndroidX preferences, and Katari keeps their values for each source and each profile. Read them with getSourcePreferences():

Kotlin
override fun setupPreferenceScreen(screen: EntryPreferenceScreen) {
    screen.addPreference(
        ListPreference(screen.context).apply {
            key = "mirror"
            title = "Mirror"
            entries = arrayOf("shows.example", "mirror.shows.example")
            entryValues = entries
            setDefaultValue("shows.example")
            summary = "%s"
        },
    )
}

override val baseUrl: String
    get() = "https://" + getSourcePreferences().getString("mirror", "shows.example")

Users reach them through Browse › Extensions, the extension, then the source's settings button. Keep every key once published, or users lose what they chose. ⋮ › Clear data on the extension's screen deletes its settings, stored logins and saved data in the open profile.

When an address reaches Katari, such as one another app shares with it, Katari asks each ResolvableSource in turn:

  1. getUriType(uri) answers quickly, without a request, whether the address belongs to the source: EntryUriType.Entry for a title, Chapter for a chapter, or Unknown. The first source that doesn't answer Unknown gets the address. An exception from any source stops the lookup with an error.
  2. getEntry(uri) returns the title, also for a chapter's address, where it is the title the chapter belongs to. Katari stores it as returned, so fill in its title. Null means the address opens nothing.
  3. For a chapter, getChapter(uri) returns it. Katari only uses its url, to find the chapter among the title's, and refreshes the chapter list once when it isn't there yet.
Kotlin
override fun getUriType(uri: String): EntryUriType {
    val url = uri.toHttpUrlOrNull() ?: return EntryUriType.Unknown
    if (url.host != "shows.example") return EntryUriType.Unknown
    return when (url.pathSegments.firstOrNull()) {
        "show" -> EntryUriType.Entry
        "watch" -> EntryUriType.Chapter
        else -> EntryUriType.Unknown
    }
}

override suspend fun getEntry(uri: String): SEntry? {
    val showUrl = when (getUriType(uri)) {
        EntryUriType.Entry -> uri
        EntryUriType.Chapter -> fetch(uri).selectFirst(".breadcrumb a.show")?.absUrl("href") ?: return null
        EntryUriType.Unknown -> return null
    }
    val entry = SEntry.create().apply {
        setUrlWithoutDomain(showUrl)
        title = ""
        type = EntryType.ANIME
    }
    return refresh(entry, emptyList(), fetchDetails = true, fetchChapters = false).entry
}

override suspend fun getChapter(uri: String): SEntryChapter? =
    SEntryChapter.create().apply {
        url = getUrlWithoutDomain(uri)
        name = ""
    }

Here refresh fills in the title, along with the rest of the details.