diff --git a/lib/extension/build.gradle b/lib/extension/build.gradle
new file mode 100644
index 000000000..a69da9c9a
--- /dev/null
+++ b/lib/extension/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 27
+ buildToolsVersion '28.0.3'
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 27
+ versionCode 1
+ versionName '1.2'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compileOnly 'com.squareup.okhttp3:okhttp:3.10.0'
+ compileOnly 'io.reactivex:rxjava:1.3.6'
+ compileOnly 'org.jsoup:jsoup:1.10.2'
+ compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+}
diff --git a/lib/extension/src/main/AndroidManifest.xml b/lib/extension/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..f07f3a03d
--- /dev/null
+++ b/lib/extension/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
new file mode 100644
index 000000000..17c64312b
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
@@ -0,0 +1,11 @@
+package eu.kanade.tachiyomi.network
+
+import android.content.Context
+import okhttp3.OkHttpClient
+
+class NetworkHelper(context: Context) {
+
+ val client: OkHttpClient = throw Exception("Stub!")
+
+ val cloudflareClient: OkHttpClient = throw Exception("Stub!")
+}
\ No newline at end of file
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
new file mode 100644
index 000000000..455f45550
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
@@ -0,0 +1,13 @@
+package eu.kanade.tachiyomi.network
+
+import okhttp3.Call
+import okhttp3.Response
+import rx.Observable
+
+fun Call.asObservable(): Observable {
+ throw Exception("Stub!")
+}
+
+fun Call.asObservableSuccess(): Observable {
+ throw Exception("Stub!")
+}
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/network/Requests.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/network/Requests.kt
new file mode 100644
index 000000000..642540ee7
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/network/Requests.kt
@@ -0,0 +1,25 @@
+package eu.kanade.tachiyomi.network
+
+import okhttp3.CacheControl
+import okhttp3.Headers
+import okhttp3.Request
+import okhttp3.RequestBody
+
+private val DEFAULT_CACHE_CONTROL: CacheControl = throw Exception("Stub!")
+private val DEFAULT_HEADERS: Headers = throw Exception("Stub!")
+private val DEFAULT_BODY: RequestBody = throw Exception("Stub!")
+
+fun GET(url: String,
+ headers: Headers = DEFAULT_HEADERS,
+ cache: CacheControl = DEFAULT_CACHE_CONTROL): Request {
+
+ throw Exception("Stub!")
+}
+
+fun POST(url: String,
+ headers: Headers = DEFAULT_HEADERS,
+ body: RequestBody = DEFAULT_BODY,
+ cache: CacheControl = DEFAULT_CACHE_CONTROL): Request {
+
+ throw Exception("Stub!")
+}
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt
new file mode 100644
index 000000000..f5f11a00b
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt
@@ -0,0 +1,46 @@
+package eu.kanade.tachiyomi.source
+
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+import rx.Observable
+
+interface CatalogueSource : Source {
+
+ /**
+ * An ISO 639-1 compliant language code (two letters in lower case).
+ */
+ val lang: String
+
+ /**
+ * Whether the source has support for latest updates.
+ */
+ val supportsLatest: Boolean
+
+ /**
+ * Returns an observable containing a page with a list of manga.
+ *
+ * @param page the page number to retrieve.
+ */
+ fun fetchPopularManga(page: Int): Observable
+
+ /**
+ * Returns an observable containing a page with a list of manga.
+ *
+ * @param page the page number to retrieve.
+ * @param query the search query.
+ * @param filters the list of filters to apply.
+ */
+ fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable
+
+ /**
+ * Returns an observable containing a page with a list of latest manga updates.
+ *
+ * @param page the page number to retrieve.
+ */
+ fun fetchLatestUpdates(page: Int): Observable
+
+ /**
+ * Returns the list of filters for the source.
+ */
+ fun getFilterList(): FilterList
+}
\ No newline at end of file
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/Source.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/Source.kt
new file mode 100644
index 000000000..7a5f43a84
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/Source.kt
@@ -0,0 +1,44 @@
+package eu.kanade.tachiyomi.source
+
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import rx.Observable
+
+/**
+ * A basic interface for creating a source. It could be an online source, a local source, etc...
+ */
+interface Source {
+
+ /**
+ * Id for the source. Must be unique.
+ */
+ val id: Long
+
+ /**
+ * Name of the source.
+ */
+ val name: String
+
+ /**
+ * Returns an observable with the updated details for a manga.
+ *
+ * @param manga the manga to update.
+ */
+ fun fetchMangaDetails(manga: SManga): Observable
+
+ /**
+ * Returns an observable with all the available chapters for a manga.
+ *
+ * @param manga the manga to update.
+ */
+ fun fetchChapterList(manga: SManga): Observable>
+
+ /**
+ * Returns an observable with the list of pages a chapter has.
+ *
+ * @param chapter the chapter.
+ */
+ fun fetchPageList(chapter: SChapter): Observable>
+
+}
\ No newline at end of file
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt
new file mode 100644
index 000000000..d326c437a
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt
@@ -0,0 +1,12 @@
+package eu.kanade.tachiyomi.source
+
+/**
+ * A factory for creating sources at runtime.
+ */
+interface SourceFactory {
+ /**
+ * Create a new copy of the sources
+ * @return The created sources
+ */
+ fun createSources(): List
+}
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt
new file mode 100644
index 000000000..a65687802
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt
@@ -0,0 +1,27 @@
+package eu.kanade.tachiyomi.source.model
+
+sealed class Filter(val name: String, var state: T) {
+ open class Header(name: String) : Filter(name, 0)
+ open class Separator(name: String = "") : Filter(name, 0)
+ abstract class Select(name: String, val values: Array, state: Int = 0) : Filter(name, state)
+ abstract class Text(name: String, state: String = "") : Filter(name, state)
+ abstract class CheckBox(name: String, state: Boolean = false) : Filter(name, state)
+ abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter(name, state) {
+ fun isIgnored() = state == STATE_IGNORE
+ fun isIncluded() = state == STATE_INCLUDE
+ fun isExcluded() = state == STATE_EXCLUDE
+
+ companion object {
+ const val STATE_IGNORE = 0
+ const val STATE_INCLUDE = 1
+ const val STATE_EXCLUDE = 2
+ }
+ }
+ abstract class Group(name: String, state: List): Filter>(name, state)
+
+ abstract class Sort(name: String, val values: Array, state: Selection? = null)
+ : Filter(name, state) {
+ data class Selection(val index: Int, val ascending: Boolean)
+ }
+
+}
\ No newline at end of file
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt
new file mode 100644
index 000000000..e24db65b6
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt
@@ -0,0 +1,7 @@
+package eu.kanade.tachiyomi.source.model
+
+data class FilterList(val list: List>) : List> by list {
+
+ constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
+
+}
\ No newline at end of file
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt
new file mode 100644
index 000000000..12dd172a7
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt
@@ -0,0 +1,3 @@
+package eu.kanade.tachiyomi.source.model
+
+data class MangasPage(val mangas: List, val hasNextPage: Boolean)
\ No newline at end of file
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt
new file mode 100644
index 000000000..39715eb2a
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt
@@ -0,0 +1,10 @@
+package eu.kanade.tachiyomi.source.model
+
+import android.net.Uri
+
+class Page(
+ val index: Int,
+ val url: String = "",
+ var imageUrl: String? = null,
+ var uri: Uri? = null
+)
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt
new file mode 100644
index 000000000..ca571b805
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt
@@ -0,0 +1,21 @@
+package eu.kanade.tachiyomi.source.model
+
+interface SChapter {
+
+ var url: String
+
+ var name: String
+
+ var date_upload: Long
+
+ var chapter_number: Float
+
+ var scanlator: String?
+
+ companion object {
+ fun create(): SChapter {
+ throw Exception("Stub!")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
new file mode 100644
index 000000000..94f48725f
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
@@ -0,0 +1,34 @@
+package eu.kanade.tachiyomi.source.model
+
+interface SManga {
+
+ var url: String
+
+ var title: String
+
+ var artist: String?
+
+ var author: String?
+
+ var description: String?
+
+ var genre: String?
+
+ var status: Int
+
+ var thumbnail_url: String?
+
+ var initialized: Boolean
+
+ companion object {
+ const val UNKNOWN = 0
+ const val ONGOING = 1
+ const val COMPLETED = 2
+ const val LICENSED = 3
+
+ fun create(): SManga {
+ throw Exception("Stub!")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
new file mode 100644
index 000000000..8002e83dd
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
@@ -0,0 +1,311 @@
+package eu.kanade.tachiyomi.source.online
+
+import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.source.CatalogueSource
+import eu.kanade.tachiyomi.source.model.*
+import okhttp3.Headers
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import rx.Observable
+
+/**
+ * A simple implementation for sources from a website.
+ */
+@Suppress("unused", "unused_parameter")
+abstract class HttpSource : CatalogueSource {
+
+ /**
+ * Network service.
+ */
+ protected val network: NetworkHelper = throw Exception("Stub!")
+
+ /**
+ * Base url of the website without the trailing slash, like: http://mysite.com
+ */
+ abstract val baseUrl: String
+
+ /**
+ * Version id used to generate the source id. If the site completely changes and urls are
+ * incompatible, you may increase this value and it'll be considered as a new source.
+ */
+ open val versionId: Int = throw Exception("Stub!")
+
+ /**
+ * Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
+ * of the MD5 of the string: sourcename/language/versionId
+ * Note the generated id sets the sign bit to 0.
+ */
+ override val id: Long = throw Exception("Stub!")
+
+ /**
+ * Headers used for requests.
+ */
+ val headers: Headers = throw Exception("Stub!")
+
+ /**
+ * Default network client for doing requests.
+ */
+ open val client: OkHttpClient = throw Exception("Stub!")
+
+ /**
+ * Headers builder for requests. Implementations can override this method for custom headers.
+ */
+ open protected fun headersBuilder(): Headers.Builder {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Visible name of the source.
+ */
+ override fun toString(): String {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns an observable containing a page with a list of manga. Normally it's not needed to
+ * override this method.
+ *
+ * @param page the page number to retrieve.
+ */
+ override fun fetchPopularManga(page: Int): Observable {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the request for the popular manga given the page.
+ *
+ * @param page the page number to retrieve.
+ */
+ abstract protected fun popularMangaRequest(page: Int): Request
+
+ /**
+ * Parses the response from the site and returns a [MangasPage] object.
+ *
+ * @param response the response from the site.
+ */
+ abstract protected fun popularMangaParse(response: Response): MangasPage
+
+ /**
+ * Returns an observable containing a page with a list of manga. Normally it's not needed to
+ * override this method.
+ *
+ * @param page the page number to retrieve.
+ * @param query the search query.
+ * @param filters the list of filters to apply.
+ */
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the request for the search manga given the page.
+ *
+ * @param page the page number to retrieve.
+ * @param query the search query.
+ * @param filters the list of filters to apply.
+ */
+ abstract protected fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request
+
+ /**
+ * Parses the response from the site and returns a [MangasPage] object.
+ *
+ * @param response the response from the site.
+ */
+ abstract protected fun searchMangaParse(response: Response): MangasPage
+
+ /**
+ * Returns an observable containing a page with a list of latest manga updates.
+ *
+ * @param page the page number to retrieve.
+ */
+ override fun fetchLatestUpdates(page: Int): Observable {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the request for latest manga given the page.
+ *
+ * @param page the page number to retrieve.
+ */
+ abstract protected fun latestUpdatesRequest(page: Int): Request
+
+ /**
+ * Parses the response from the site and returns a [MangasPage] object.
+ *
+ * @param response the response from the site.
+ */
+ abstract protected fun latestUpdatesParse(response: Response): MangasPage
+
+ /**
+ * Returns an observable with the updated details for a manga. Normally it's not needed to
+ * override this method.
+ *
+ * @param manga the manga to be updated.
+ */
+ override fun fetchMangaDetails(manga: SManga): Observable {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the request for the details of a manga. Override only if it's needed to change the
+ * url, send different headers or request method like POST.
+ *
+ * @param manga the manga to be updated.
+ */
+ open fun mangaDetailsRequest(manga: SManga): Request {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Parses the response from the site and returns the details of a manga.
+ *
+ * @param response the response from the site.
+ */
+ abstract protected fun mangaDetailsParse(response: Response): SManga
+
+ /**
+ * Returns an observable with the updated chapter list for a manga. Normally it's not needed to
+ * override this method.
+ *
+ * @param manga the manga to look for chapters.
+ */
+ override fun fetchChapterList(manga: SManga): Observable> {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the request for updating the chapter list. Override only if it's needed to override
+ * the url, send different headers or request method like POST.
+ *
+ * @param manga the manga to look for chapters.
+ */
+ open protected fun chapterListRequest(manga: SManga): Request {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Parses the response from the site and returns a list of chapters.
+ *
+ * @param response the response from the site.
+ */
+ abstract protected fun chapterListParse(response: Response): List
+
+ /**
+ * Returns an observable with the page list for a chapter.
+ *
+ * @param chapter the chapter whose page list has to be fetched.
+ */
+ override fun fetchPageList(chapter: SChapter): Observable> {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the request for getting the page list. Override only if it's needed to override the
+ * url, send different headers or request method like POST.
+ *
+ * @param chapter the chapter whose page list has to be fetched.
+ */
+ open protected fun pageListRequest(chapter: SChapter): Request {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Parses the response from the site and returns a list of pages.
+ *
+ * @param response the response from the site.
+ */
+ abstract protected fun pageListParse(response: Response): List
+
+ /**
+ * Returns an observable with the page containing the source url of the image. If there's any
+ * error, it will return null instead of throwing an exception.
+ *
+ * @param page the page whose source image has to be fetched.
+ */
+ open fun fetchImageUrl(page: Page): Observable {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the request for getting the url to the source image. Override only if it's needed to
+ * override the url, send different headers or request method like POST.
+ *
+ * @param page the chapter whose page list has to be fetched
+ */
+ open protected fun imageUrlRequest(page: Page): Request {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Parses the response from the site and returns the absolute url to the source image.
+ *
+ * @param response the response from the site.
+ */
+ abstract protected fun imageUrlParse(response: Response): String
+
+ /**
+ * Returns an observable with the response of the source image.
+ *
+ * @param page the page whose source image has to be downloaded.
+ */
+ fun fetchImage(page: Page): Observable {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the request for getting the source image. Override only if it's needed to override
+ * the url, send different headers or request method like POST.
+ *
+ * @param page the chapter whose page list has to be fetched
+ */
+ open protected fun imageRequest(page: Page): Request {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Assigns the url of the chapter without the scheme and domain. It saves some redundancy from
+ * database and the urls could still work after a domain change.
+ *
+ * @param url the full url to the chapter.
+ */
+ fun SChapter.setUrlWithoutDomain(url: String) {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Assigns the url of the manga without the scheme and domain. It saves some redundancy from
+ * database and the urls could still work after a domain change.
+ *
+ * @param url the full url to the manga.
+ */
+ fun SManga.setUrlWithoutDomain(url: String) {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the url of the given string without the scheme and domain.
+ *
+ * @param orig the full url.
+ */
+ private fun getUrlWithoutDomain(orig: String): String {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Called before inserting a new chapter into database. Use it if you need to override chapter
+ * fields, like the title or the chapter number. Do not change anything to [manga].
+ *
+ * @param chapter the chapter to be added.
+ * @param manga the manga of the chapter.
+ */
+ open fun prepareNewChapter(chapter: SChapter, manga: SManga) {
+ }
+
+ /**
+ * Returns the list of filters for the source.
+ */
+ override fun getFilterList(): FilterList {
+ throw Exception("Stub!")
+ }
+}
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt
new file mode 100644
index 000000000..2707fcc16
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt
@@ -0,0 +1,169 @@
+package eu.kanade.tachiyomi.source.online
+
+import eu.kanade.tachiyomi.source.model.MangasPage
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import okhttp3.Response
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+
+/**
+ * A simple implementation for sources from a website using Jsoup, an HTML parser.
+ */
+@Suppress("unused", "unused_parameter")
+abstract class ParsedHttpSource : HttpSource() {
+
+ /**
+ * Parses the response from the site and returns a [MangasPage] object.
+ *
+ * @param response the response from the site.
+ */
+ override fun popularMangaParse(response: Response): MangasPage {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
+ */
+ abstract protected fun popularMangaSelector(): String
+
+ /**
+ * Returns a manga from the given [element]. Most sites only show the title and the url, it's
+ * totally fine to fill only those two values.
+ *
+ * @param element an element obtained from [popularMangaSelector].
+ */
+ abstract protected fun popularMangaFromElement(element: Element): SManga
+
+ /**
+ * Returns the Jsoup selector that returns the tag linking to the next page, or null if
+ * there's no next page.
+ */
+ abstract protected fun popularMangaNextPageSelector(): String?
+
+ /**
+ * Parses the response from the site and returns a [MangasPage] object.
+ *
+ * @param response the response from the site.
+ */
+ override fun searchMangaParse(response: Response): MangasPage {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
+ */
+ abstract protected fun searchMangaSelector(): String
+
+ /**
+ * Returns a manga from the given [element]. Most sites only show the title and the url, it's
+ * totally fine to fill only those two values.
+ *
+ * @param element an element obtained from [searchMangaSelector].
+ */
+ abstract protected fun searchMangaFromElement(element: Element): SManga
+
+ /**
+ * Returns the Jsoup selector that returns the tag linking to the next page, or null if
+ * there's no next page.
+ */
+ abstract protected fun searchMangaNextPageSelector(): String?
+
+ /**
+ * Parses the response from the site and returns a [MangasPage] object.
+ *
+ * @param response the response from the site.
+ */
+ override fun latestUpdatesParse(response: Response): MangasPage {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
+ */
+ abstract protected fun latestUpdatesSelector(): String
+
+ /**
+ * Returns a manga from the given [element]. Most sites only show the title and the url, it's
+ * totally fine to fill only those two values.
+ *
+ * @param element an element obtained from [latestUpdatesSelector].
+ */
+ abstract protected fun latestUpdatesFromElement(element: Element): SManga
+
+ /**
+ * Returns the Jsoup selector that returns the tag linking to the next page, or null if
+ * there's no next page.
+ */
+ abstract protected fun latestUpdatesNextPageSelector(): String?
+
+ /**
+ * Parses the response from the site and returns the details of a manga.
+ *
+ * @param response the response from the site.
+ */
+ override fun mangaDetailsParse(response: Response): SManga {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the details of the manga from the given [document].
+ *
+ * @param document the parsed document.
+ */
+ abstract protected fun mangaDetailsParse(document: Document): SManga
+
+ /**
+ * Parses the response from the site and returns a list of chapters.
+ *
+ * @param response the response from the site.
+ */
+ override fun chapterListParse(response: Response): List {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter.
+ */
+ abstract protected fun chapterListSelector(): String
+
+ /**
+ * Returns a chapter from the given element.
+ *
+ * @param element an element obtained from [chapterListSelector].
+ */
+ abstract protected fun chapterFromElement(element: Element): SChapter
+
+ /**
+ * Parses the response from the site and returns the page list.
+ *
+ * @param response the response from the site.
+ */
+ override fun pageListParse(response: Response): List {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns a page list from the given document.
+ *
+ * @param document the parsed document.
+ */
+ abstract protected fun pageListParse(document: Document): List
+
+ /**
+ * Parse the response from the site and returns the absolute url to the source image.
+ *
+ * @param response the response from the site.
+ */
+ override fun imageUrlParse(response: Response): String {
+ throw Exception("Stub!")
+ }
+
+ /**
+ * Returns the absolute url to the source image from the document.
+ *
+ * @param document the parsed document.
+ */
+ abstract protected fun imageUrlParse(document: Document): String
+}
diff --git a/lib/extension/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt b/lib/extension/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt
new file mode 100644
index 000000000..bb61bc147
--- /dev/null
+++ b/lib/extension/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt
@@ -0,0 +1,12 @@
+package eu.kanade.tachiyomi.util
+
+import okhttp3.Response
+import org.jsoup.nodes.Document
+
+/**
+ * Returns a Jsoup document for this response.
+ * @param html the body of the response. Use only if the body was read before calling this method.
+ */
+fun Response.asJsoup(html: String? = null): Document {
+ throw Exception("Stub!")
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index ae38f5c09..0c1be2acf 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,10 +1,5 @@
-new File(rootDir, "src").eachDir { dir ->
- dir.eachDir { subdir ->
- String name = ":${dir.name}-${subdir.name}"
- include name
- project(name).projectDir = new File("src/${dir.name}/${subdir.name}")
- }
-}
+include ':lib-extension'
+project(':lib-extension').projectDir = new File("lib/extension")
include ':lib-ratelimit'
project(':lib-ratelimit').projectDir = new File("lib/ratelimit")
@@ -14,3 +9,11 @@ project(':duktape-stub').projectDir = new File("lib/duktape-stub")
include ':preference-stub'
project(':preference-stub').projectDir = new File("lib/preference-stub")
+
+new File(rootDir, "src").eachDir { dir ->
+ dir.eachDir { subdir ->
+ String name = ":${dir.name}-${subdir.name}"
+ include name
+ project(name).projectDir = new File("src/${dir.name}/${subdir.name}")
+ }
+}